@@ -1,322 +1,328 | |||||
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 | scope :sorted, lambda { order("#{table_name}.position ASC") } |
|
33 | scope :sorted, lambda { order("#{table_name}.position ASC") } | |
34 |
|
34 | |||
35 | CUSTOM_FIELDS_TABS = [ |
|
35 | CUSTOM_FIELDS_TABS = [ | |
36 | {:name => 'IssueCustomField', :partial => 'custom_fields/index', |
|
36 | {:name => 'IssueCustomField', :partial => 'custom_fields/index', | |
37 | :label => :label_issue_plural}, |
|
37 | :label => :label_issue_plural}, | |
38 | {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', |
|
38 | {:name => 'TimeEntryCustomField', :partial => 'custom_fields/index', | |
39 | :label => :label_spent_time}, |
|
39 | :label => :label_spent_time}, | |
40 | {:name => 'ProjectCustomField', :partial => 'custom_fields/index', |
|
40 | {:name => 'ProjectCustomField', :partial => 'custom_fields/index', | |
41 | :label => :label_project_plural}, |
|
41 | :label => :label_project_plural}, | |
42 | {:name => 'VersionCustomField', :partial => 'custom_fields/index', |
|
42 | {:name => 'VersionCustomField', :partial => 'custom_fields/index', | |
43 | :label => :label_version_plural}, |
|
43 | :label => :label_version_plural}, | |
44 | {:name => 'UserCustomField', :partial => 'custom_fields/index', |
|
44 | {:name => 'UserCustomField', :partial => 'custom_fields/index', | |
45 | :label => :label_user_plural}, |
|
45 | :label => :label_user_plural}, | |
46 | {:name => 'GroupCustomField', :partial => 'custom_fields/index', |
|
46 | {:name => 'GroupCustomField', :partial => 'custom_fields/index', | |
47 | :label => :label_group_plural}, |
|
47 | :label => :label_group_plural}, | |
48 | {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', |
|
48 | {:name => 'TimeEntryActivityCustomField', :partial => 'custom_fields/index', | |
49 | :label => TimeEntryActivity::OptionName}, |
|
49 | :label => TimeEntryActivity::OptionName}, | |
50 | {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', |
|
50 | {:name => 'IssuePriorityCustomField', :partial => 'custom_fields/index', | |
51 | :label => IssuePriority::OptionName}, |
|
51 | :label => IssuePriority::OptionName}, | |
52 | {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', |
|
52 | {:name => 'DocumentCategoryCustomField', :partial => 'custom_fields/index', | |
53 | :label => DocumentCategory::OptionName} |
|
53 | :label => DocumentCategory::OptionName} | |
54 | ] |
|
54 | ] | |
55 |
|
55 | |||
56 | CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]} |
|
56 | CUSTOM_FIELDS_NAMES = CUSTOM_FIELDS_TABS.collect{|v| v[:name]} | |
57 |
|
57 | |||
58 | def field_format=(arg) |
|
58 | def field_format=(arg) | |
59 | # cannot change format of a saved custom field |
|
59 | # cannot change format of a saved custom field | |
60 | super if new_record? |
|
60 | super if new_record? | |
61 | end |
|
61 | end | |
62 |
|
62 | |||
63 | def set_searchable |
|
63 | def set_searchable | |
64 | # make sure these fields are not searchable |
|
64 | # make sure these fields are not searchable | |
65 | self.searchable = false if %w(int float date bool).include?(field_format) |
|
65 | self.searchable = false if %w(int float date bool).include?(field_format) | |
66 | # make sure only these fields can have multiple values |
|
66 | # make sure only these fields can have multiple values | |
67 | self.multiple = false unless %w(list user version).include?(field_format) |
|
67 | self.multiple = false unless %w(list user version).include?(field_format) | |
68 | true |
|
68 | true | |
69 | end |
|
69 | end | |
70 |
|
70 | |||
71 | def validate_custom_field |
|
71 | def validate_custom_field | |
72 | if self.field_format == "list" |
|
72 | if self.field_format == "list" | |
73 | errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? |
|
73 | errors.add(:possible_values, :blank) if self.possible_values.nil? || self.possible_values.empty? | |
74 | errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array |
|
74 | errors.add(:possible_values, :invalid) unless self.possible_values.is_a? Array | |
75 | end |
|
75 | end | |
76 |
|
76 | |||
77 | if regexp.present? |
|
77 | if regexp.present? | |
78 | begin |
|
78 | begin | |
79 | Regexp.new(regexp) |
|
79 | Regexp.new(regexp) | |
80 | rescue |
|
80 | rescue | |
81 | errors.add(:regexp, :invalid) |
|
81 | errors.add(:regexp, :invalid) | |
82 | end |
|
82 | end | |
83 | end |
|
83 | end | |
84 |
|
84 | |||
85 | if default_value.present? && !valid_field_value?(default_value) |
|
85 | if default_value.present? && !valid_field_value?(default_value) | |
86 | errors.add(:default_value, :invalid) |
|
86 | errors.add(:default_value, :invalid) | |
87 | end |
|
87 | end | |
88 | end |
|
88 | end | |
89 |
|
89 | |||
90 | def possible_values_options(obj=nil) |
|
90 | def possible_values_options(obj=nil) | |
91 | case field_format |
|
91 | case field_format | |
92 | when 'user', 'version' |
|
92 | when 'user', 'version' | |
93 | if obj.respond_to?(:project) && obj.project |
|
93 | if obj.respond_to?(:project) && obj.project | |
94 | case field_format |
|
94 | case field_format | |
95 | when 'user' |
|
95 | when 'user' | |
96 | obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} |
|
96 | obj.project.users.sort.collect {|u| [u.to_s, u.id.to_s]} | |
97 | when 'version' |
|
97 | when 'version' | |
98 | obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} |
|
98 | obj.project.shared_versions.sort.collect {|u| [u.to_s, u.id.to_s]} | |
99 | end |
|
99 | end | |
100 | elsif obj.is_a?(Array) |
|
100 | elsif obj.is_a?(Array) | |
101 | obj.collect {|o| possible_values_options(o)}.reduce(:&) |
|
101 | obj.collect {|o| possible_values_options(o)}.reduce(:&) | |
102 | else |
|
102 | else | |
103 | [] |
|
103 | [] | |
104 | end |
|
104 | end | |
105 | when 'bool' |
|
105 | when 'bool' | |
106 | [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']] |
|
106 | [[l(:general_text_Yes), '1'], [l(:general_text_No), '0']] | |
107 | else |
|
107 | else | |
108 | possible_values || [] |
|
108 | possible_values || [] | |
109 | end |
|
109 | end | |
110 | end |
|
110 | end | |
111 |
|
111 | |||
112 | def possible_values(obj=nil) |
|
112 | def possible_values(obj=nil) | |
113 | case field_format |
|
113 | case field_format | |
114 | when 'user', 'version' |
|
114 | when 'user', 'version' | |
115 | possible_values_options(obj).collect(&:last) |
|
115 | possible_values_options(obj).collect(&:last) | |
116 | when 'bool' |
|
116 | when 'bool' | |
117 | ['1', '0'] |
|
117 | ['1', '0'] | |
118 | else |
|
118 | else | |
119 | values = super() |
|
119 | values = super() | |
120 | if values.is_a?(Array) |
|
120 | if values.is_a?(Array) | |
121 | values.each do |value| |
|
121 | values.each do |value| | |
122 | value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) |
|
122 | value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) | |
123 | end |
|
123 | end | |
124 | end |
|
124 | end | |
125 | values || [] |
|
125 | values || [] | |
126 | end |
|
126 | end | |
127 | end |
|
127 | end | |
128 |
|
128 | |||
129 | # Makes possible_values accept a multiline string |
|
129 | # Makes possible_values accept a multiline string | |
130 | def possible_values=(arg) |
|
130 | def possible_values=(arg) | |
131 | if arg.is_a?(Array) |
|
131 | if arg.is_a?(Array) | |
132 | super(arg.compact.collect(&:strip).select {|v| !v.blank?}) |
|
132 | super(arg.compact.collect(&:strip).select {|v| !v.blank?}) | |
133 | else |
|
133 | else | |
134 | self.possible_values = arg.to_s.split(/[\n\r]+/) |
|
134 | self.possible_values = arg.to_s.split(/[\n\r]+/) | |
135 | end |
|
135 | end | |
136 | end |
|
136 | end | |
137 |
|
137 | |||
138 | def cast_value(value) |
|
138 | def cast_value(value) | |
139 | casted = nil |
|
139 | casted = nil | |
140 | unless value.blank? |
|
140 | unless value.blank? | |
141 | case field_format |
|
141 | case field_format | |
142 | when 'string', 'text', 'list' |
|
142 | when 'string', 'text', 'list' | |
143 | casted = value |
|
143 | casted = value | |
144 | when 'date' |
|
144 | when 'date' | |
145 | casted = begin; value.to_date; rescue; nil end |
|
145 | casted = begin; value.to_date; rescue; nil end | |
146 | when 'bool' |
|
146 | when 'bool' | |
147 | casted = (value == '1' ? true : false) |
|
147 | casted = (value == '1' ? true : false) | |
148 | when 'int' |
|
148 | when 'int' | |
149 | casted = value.to_i |
|
149 | casted = value.to_i | |
150 | when 'float' |
|
150 | when 'float' | |
151 | casted = value.to_f |
|
151 | casted = value.to_f | |
152 | when 'user', 'version' |
|
152 | when 'user', 'version' | |
153 | casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) |
|
153 | casted = (value.blank? ? nil : field_format.classify.constantize.find_by_id(value.to_i)) | |
154 | end |
|
154 | end | |
155 | end |
|
155 | end | |
156 | casted |
|
156 | casted | |
157 | end |
|
157 | end | |
158 |
|
158 | |||
159 | def value_from_keyword(keyword, customized) |
|
159 | def value_from_keyword(keyword, customized) | |
160 | possible_values_options = possible_values_options(customized) |
|
160 | possible_values_options = possible_values_options(customized) | |
161 | if possible_values_options.present? |
|
161 | if possible_values_options.present? | |
162 | keyword = keyword.to_s.downcase |
|
162 | keyword = keyword.to_s.downcase | |
163 |
possible_values_options.detect {|text, id| text.downcase == keyword} |
|
163 | if v = possible_values_options.detect {|text, id| text.downcase == keyword} | |
|
164 | if v.is_a?(Array) | |||
|
165 | v.last | |||
|
166 | else | |||
|
167 | v | |||
|
168 | end | |||
|
169 | end | |||
164 | else |
|
170 | else | |
165 | keyword |
|
171 | keyword | |
166 | end |
|
172 | end | |
167 | end |
|
173 | end | |
168 |
|
174 | |||
169 | # Returns a ORDER BY clause that can used to sort customized |
|
175 | # Returns a ORDER BY clause that can used to sort customized | |
170 | # objects by their value of the custom field. |
|
176 | # objects by their value of the custom field. | |
171 | # Returns nil if the custom field can not be used for sorting. |
|
177 | # Returns nil if the custom field can not be used for sorting. | |
172 | def order_statement |
|
178 | def order_statement | |
173 | return nil if multiple? |
|
179 | return nil if multiple? | |
174 | case field_format |
|
180 | case field_format | |
175 | when 'string', 'text', 'list', 'date', 'bool' |
|
181 | when 'string', 'text', 'list', 'date', 'bool' | |
176 | # COALESCE is here to make sure that blank and NULL values are sorted equally |
|
182 | # COALESCE is here to make sure that blank and NULL values are sorted equally | |
177 | "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + |
|
183 | "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + | |
178 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + |
|
184 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + | |
179 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + |
|
185 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + | |
180 | " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" |
|
186 | " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" | |
181 | when 'int', 'float' |
|
187 | when 'int', 'float' | |
182 | # Make the database cast values into numeric |
|
188 | # Make the database cast values into numeric | |
183 | # Postgresql will raise an error if a value can not be casted! |
|
189 | # Postgresql will raise an error if a value can not be casted! | |
184 | # CustomValue validations should ensure that it doesn't occur |
|
190 | # CustomValue validations should ensure that it doesn't occur | |
185 | "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" + |
|
191 | "(SELECT CAST(cv_sort.value AS decimal(60,3)) FROM #{CustomValue.table_name} cv_sort" + | |
186 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + |
|
192 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + | |
187 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + |
|
193 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + | |
188 | " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)" |
|
194 | " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)" | |
189 | when 'user', 'version' |
|
195 | when 'user', 'version' | |
190 | value_class.fields_for_order_statement(value_join_alias) |
|
196 | value_class.fields_for_order_statement(value_join_alias) | |
191 | else |
|
197 | else | |
192 | nil |
|
198 | nil | |
193 | end |
|
199 | end | |
194 | end |
|
200 | end | |
195 |
|
201 | |||
196 | # Returns a GROUP BY clause that can used to group by custom value |
|
202 | # Returns a GROUP BY clause that can used to group by custom value | |
197 | # Returns nil if the custom field can not be used for grouping. |
|
203 | # Returns nil if the custom field can not be used for grouping. | |
198 | def group_statement |
|
204 | def group_statement | |
199 | return nil if multiple? |
|
205 | return nil if multiple? | |
200 | case field_format |
|
206 | case field_format | |
201 | when 'list', 'date', 'bool', 'int' |
|
207 | when 'list', 'date', 'bool', 'int' | |
202 | order_statement |
|
208 | order_statement | |
203 | when 'user', 'version' |
|
209 | when 'user', 'version' | |
204 | "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + |
|
210 | "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" + | |
205 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + |
|
211 | " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" + | |
206 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + |
|
212 | " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" + | |
207 | " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" |
|
213 | " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')" | |
208 | else |
|
214 | else | |
209 | nil |
|
215 | nil | |
210 | end |
|
216 | end | |
211 | end |
|
217 | end | |
212 |
|
218 | |||
213 | def join_for_order_statement |
|
219 | def join_for_order_statement | |
214 | case field_format |
|
220 | case field_format | |
215 | when 'user', 'version' |
|
221 | when 'user', 'version' | |
216 | "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + |
|
222 | "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" + | |
217 | " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + |
|
223 | " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" + | |
218 | " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + |
|
224 | " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" + | |
219 | " AND #{join_alias}.custom_field_id = #{id}" + |
|
225 | " AND #{join_alias}.custom_field_id = #{id}" + | |
220 | " AND #{join_alias}.value <> ''" + |
|
226 | " AND #{join_alias}.value <> ''" + | |
221 | " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + |
|
227 | " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" + | |
222 | " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + |
|
228 | " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" + | |
223 | " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + |
|
229 | " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" + | |
224 | " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + |
|
230 | " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" + | |
225 | " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + |
|
231 | " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" + | |
226 | " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id" |
|
232 | " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id" | |
227 | else |
|
233 | else | |
228 | nil |
|
234 | nil | |
229 | end |
|
235 | end | |
230 | end |
|
236 | end | |
231 |
|
237 | |||
232 | def join_alias |
|
238 | def join_alias | |
233 | "cf_#{id}" |
|
239 | "cf_#{id}" | |
234 | end |
|
240 | end | |
235 |
|
241 | |||
236 | def value_join_alias |
|
242 | def value_join_alias | |
237 | join_alias + "_" + field_format |
|
243 | join_alias + "_" + field_format | |
238 | end |
|
244 | end | |
239 |
|
245 | |||
240 | def <=>(field) |
|
246 | def <=>(field) | |
241 | position <=> field.position |
|
247 | position <=> field.position | |
242 | end |
|
248 | end | |
243 |
|
249 | |||
244 | # Returns the class that values represent |
|
250 | # Returns the class that values represent | |
245 | def value_class |
|
251 | def value_class | |
246 | case field_format |
|
252 | case field_format | |
247 | when 'user', 'version' |
|
253 | when 'user', 'version' | |
248 | field_format.classify.constantize |
|
254 | field_format.classify.constantize | |
249 | else |
|
255 | else | |
250 | nil |
|
256 | nil | |
251 | end |
|
257 | end | |
252 | end |
|
258 | end | |
253 |
|
259 | |||
254 | def self.customized_class |
|
260 | def self.customized_class | |
255 | self.name =~ /^(.+)CustomField$/ |
|
261 | self.name =~ /^(.+)CustomField$/ | |
256 | begin; $1.constantize; rescue nil; end |
|
262 | begin; $1.constantize; rescue nil; end | |
257 | end |
|
263 | end | |
258 |
|
264 | |||
259 | # to move in project_custom_field |
|
265 | # to move in project_custom_field | |
260 | def self.for_all |
|
266 | def self.for_all | |
261 | where(:is_for_all => true).order('position').all |
|
267 | where(:is_for_all => true).order('position').all | |
262 | end |
|
268 | end | |
263 |
|
269 | |||
264 | def type_name |
|
270 | def type_name | |
265 | nil |
|
271 | nil | |
266 | end |
|
272 | end | |
267 |
|
273 | |||
268 | # Returns the error messages for the given value |
|
274 | # Returns the error messages for the given value | |
269 | # or an empty array if value is a valid value for the custom field |
|
275 | # or an empty array if value is a valid value for the custom field | |
270 | def validate_field_value(value) |
|
276 | def validate_field_value(value) | |
271 | errs = [] |
|
277 | errs = [] | |
272 | if value.is_a?(Array) |
|
278 | if value.is_a?(Array) | |
273 | if !multiple? |
|
279 | if !multiple? | |
274 | errs << ::I18n.t('activerecord.errors.messages.invalid') |
|
280 | errs << ::I18n.t('activerecord.errors.messages.invalid') | |
275 | end |
|
281 | end | |
276 | if is_required? && value.detect(&:present?).nil? |
|
282 | if is_required? && value.detect(&:present?).nil? | |
277 | errs << ::I18n.t('activerecord.errors.messages.blank') |
|
283 | errs << ::I18n.t('activerecord.errors.messages.blank') | |
278 | end |
|
284 | end | |
279 | value.each {|v| errs += validate_field_value_format(v)} |
|
285 | value.each {|v| errs += validate_field_value_format(v)} | |
280 | else |
|
286 | else | |
281 | if is_required? && value.blank? |
|
287 | if is_required? && value.blank? | |
282 | errs << ::I18n.t('activerecord.errors.messages.blank') |
|
288 | errs << ::I18n.t('activerecord.errors.messages.blank') | |
283 | end |
|
289 | end | |
284 | errs += validate_field_value_format(value) |
|
290 | errs += validate_field_value_format(value) | |
285 | end |
|
291 | end | |
286 | errs |
|
292 | errs | |
287 | end |
|
293 | end | |
288 |
|
294 | |||
289 | # Returns true if value is a valid value for the custom field |
|
295 | # Returns true if value is a valid value for the custom field | |
290 | def valid_field_value?(value) |
|
296 | def valid_field_value?(value) | |
291 | validate_field_value(value).empty? |
|
297 | validate_field_value(value).empty? | |
292 | end |
|
298 | end | |
293 |
|
299 | |||
294 | def format_in?(*args) |
|
300 | def format_in?(*args) | |
295 | args.include?(field_format) |
|
301 | args.include?(field_format) | |
296 | end |
|
302 | end | |
297 |
|
303 | |||
298 | protected |
|
304 | protected | |
299 |
|
305 | |||
300 | # Returns the error message for the given value regarding its format |
|
306 | # Returns the error message for the given value regarding its format | |
301 | def validate_field_value_format(value) |
|
307 | def validate_field_value_format(value) | |
302 | errs = [] |
|
308 | errs = [] | |
303 | if value.present? |
|
309 | if value.present? | |
304 | errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) |
|
310 | errs << ::I18n.t('activerecord.errors.messages.invalid') unless regexp.blank? or value =~ Regexp.new(regexp) | |
305 | errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length |
|
311 | errs << ::I18n.t('activerecord.errors.messages.too_short', :count => min_length) if min_length > 0 and value.length < min_length | |
306 | errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length |
|
312 | errs << ::I18n.t('activerecord.errors.messages.too_long', :count => max_length) if max_length > 0 and value.length > max_length | |
307 |
|
313 | |||
308 | # Format specific validations |
|
314 | # Format specific validations | |
309 | case field_format |
|
315 | case field_format | |
310 | when 'int' |
|
316 | when 'int' | |
311 | errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ |
|
317 | errs << ::I18n.t('activerecord.errors.messages.not_a_number') unless value =~ /^[+-]?\d+$/ | |
312 | when 'float' |
|
318 | when 'float' | |
313 | begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end |
|
319 | begin; Kernel.Float(value); rescue; errs << ::I18n.t('activerecord.errors.messages.invalid') end | |
314 | when 'date' |
|
320 | when 'date' | |
315 | errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end |
|
321 | errs << ::I18n.t('activerecord.errors.messages.not_a_date') unless value =~ /^\d{4}-\d{2}-\d{2}$/ && begin; value.to_date; rescue; false end | |
316 | when 'list' |
|
322 | when 'list' | |
317 | errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) |
|
323 | errs << ::I18n.t('activerecord.errors.messages.inclusion') unless possible_values.include?(value) | |
318 | end |
|
324 | end | |
319 | end |
|
325 | end | |
320 | errs |
|
326 | errs | |
321 | end |
|
327 | end | |
322 | end |
|
328 | 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,221 +1,226 | |||||
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_should_not_change_field_format_of_existing_custom_field |
|
60 | def test_should_not_change_field_format_of_existing_custom_field | |
61 | field = CustomField.find(1) |
|
61 | field = CustomField.find(1) | |
62 | field.field_format = 'int' |
|
62 | field.field_format = 'int' | |
63 | assert_equal 'list', field.field_format |
|
63 | assert_equal 'list', field.field_format | |
64 | end |
|
64 | end | |
65 |
|
65 | |||
66 | def test_possible_values_should_accept_an_array |
|
66 | def test_possible_values_should_accept_an_array | |
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_string |
|
72 | def test_possible_values_should_accept_a_string | |
73 | field = CustomField.new |
|
73 | field = CustomField.new | |
74 | field.possible_values = "One value" |
|
74 | field.possible_values = "One value" | |
75 | assert_equal ["One value"], field.possible_values |
|
75 | assert_equal ["One value"], field.possible_values | |
76 | end |
|
76 | end | |
77 |
|
77 | |||
78 | def test_possible_values_should_accept_a_multiline_string |
|
78 | def test_possible_values_should_accept_a_multiline_string | |
79 | field = CustomField.new |
|
79 | field = CustomField.new | |
80 | field.possible_values = "One value\nAnd another one \r\n \n" |
|
80 | field.possible_values = "One value\nAnd another one \r\n \n" | |
81 | assert_equal ["One value", "And another one"], field.possible_values |
|
81 | assert_equal ["One value", "And another one"], field.possible_values | |
82 | end |
|
82 | end | |
83 |
|
83 | |||
84 | if "string".respond_to?(:encoding) |
|
84 | if "string".respond_to?(:encoding) | |
85 | def test_possible_values_stored_as_binary_should_be_utf8_encoded |
|
85 | def test_possible_values_stored_as_binary_should_be_utf8_encoded | |
86 | field = CustomField.find(11) |
|
86 | field = CustomField.find(11) | |
87 | assert_kind_of Array, field.possible_values |
|
87 | assert_kind_of Array, field.possible_values | |
88 | assert field.possible_values.size > 0 |
|
88 | assert field.possible_values.size > 0 | |
89 | field.possible_values.each do |value| |
|
89 | field.possible_values.each do |value| | |
90 | assert_equal "UTF-8", value.encoding.name |
|
90 | assert_equal "UTF-8", value.encoding.name | |
91 | end |
|
91 | end | |
92 | end |
|
92 | end | |
93 | end |
|
93 | end | |
94 |
|
94 | |||
95 | def test_destroy |
|
95 | def test_destroy | |
96 | field = CustomField.find(1) |
|
96 | field = CustomField.find(1) | |
97 | assert field.destroy |
|
97 | assert field.destroy | |
98 | end |
|
98 | end | |
99 |
|
99 | |||
100 | def test_new_subclass_instance_should_return_an_instance |
|
100 | def test_new_subclass_instance_should_return_an_instance | |
101 | f = CustomField.new_subclass_instance('IssueCustomField') |
|
101 | f = CustomField.new_subclass_instance('IssueCustomField') | |
102 | assert_kind_of IssueCustomField, f |
|
102 | assert_kind_of IssueCustomField, f | |
103 | end |
|
103 | end | |
104 |
|
104 | |||
105 | def test_new_subclass_instance_should_set_attributes |
|
105 | def test_new_subclass_instance_should_set_attributes | |
106 | f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test') |
|
106 | f = CustomField.new_subclass_instance('IssueCustomField', :name => 'Test') | |
107 | assert_kind_of IssueCustomField, f |
|
107 | assert_kind_of IssueCustomField, f | |
108 | assert_equal 'Test', f.name |
|
108 | assert_equal 'Test', f.name | |
109 | end |
|
109 | end | |
110 |
|
110 | |||
111 | def test_new_subclass_instance_with_invalid_class_name_should_return_nil |
|
111 | def test_new_subclass_instance_with_invalid_class_name_should_return_nil | |
112 | assert_nil CustomField.new_subclass_instance('WrongClassName') |
|
112 | assert_nil CustomField.new_subclass_instance('WrongClassName') | |
113 | end |
|
113 | end | |
114 |
|
114 | |||
115 | def test_new_subclass_instance_with_non_subclass_name_should_return_nil |
|
115 | def test_new_subclass_instance_with_non_subclass_name_should_return_nil | |
116 | assert_nil CustomField.new_subclass_instance('Project') |
|
116 | assert_nil CustomField.new_subclass_instance('Project') | |
117 | end |
|
117 | end | |
118 |
|
118 | |||
119 | def test_string_field_validation_with_blank_value |
|
119 | def test_string_field_validation_with_blank_value | |
120 | f = CustomField.new(:field_format => 'string') |
|
120 | f = CustomField.new(:field_format => 'string') | |
121 |
|
121 | |||
122 | assert f.valid_field_value?(nil) |
|
122 | assert f.valid_field_value?(nil) | |
123 | assert f.valid_field_value?('') |
|
123 | assert f.valid_field_value?('') | |
124 |
|
124 | |||
125 | f.is_required = true |
|
125 | f.is_required = true | |
126 | assert !f.valid_field_value?(nil) |
|
126 | assert !f.valid_field_value?(nil) | |
127 | assert !f.valid_field_value?('') |
|
127 | assert !f.valid_field_value?('') | |
128 | end |
|
128 | end | |
129 |
|
129 | |||
130 | def test_string_field_validation_with_min_and_max_lengths |
|
130 | def test_string_field_validation_with_min_and_max_lengths | |
131 | f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) |
|
131 | f = CustomField.new(:field_format => 'string', :min_length => 2, :max_length => 5) | |
132 |
|
132 | |||
133 | assert f.valid_field_value?(nil) |
|
133 | assert f.valid_field_value?(nil) | |
134 | assert f.valid_field_value?('') |
|
134 | assert f.valid_field_value?('') | |
135 | assert f.valid_field_value?('a' * 2) |
|
135 | assert f.valid_field_value?('a' * 2) | |
136 | assert !f.valid_field_value?('a') |
|
136 | assert !f.valid_field_value?('a') | |
137 | assert !f.valid_field_value?('a' * 6) |
|
137 | assert !f.valid_field_value?('a' * 6) | |
138 | end |
|
138 | end | |
139 |
|
139 | |||
140 | def test_string_field_validation_with_regexp |
|
140 | def test_string_field_validation_with_regexp | |
141 | f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') |
|
141 | f = CustomField.new(:field_format => 'string', :regexp => '^[A-Z0-9]*$') | |
142 |
|
142 | |||
143 | assert f.valid_field_value?(nil) |
|
143 | assert f.valid_field_value?(nil) | |
144 | assert f.valid_field_value?('') |
|
144 | assert f.valid_field_value?('') | |
145 | assert f.valid_field_value?('ABC') |
|
145 | assert f.valid_field_value?('ABC') | |
146 | assert !f.valid_field_value?('abc') |
|
146 | assert !f.valid_field_value?('abc') | |
147 | end |
|
147 | end | |
148 |
|
148 | |||
149 | def test_date_field_validation |
|
149 | def test_date_field_validation | |
150 | f = CustomField.new(:field_format => 'date') |
|
150 | f = CustomField.new(:field_format => 'date') | |
151 |
|
151 | |||
152 | assert f.valid_field_value?(nil) |
|
152 | assert f.valid_field_value?(nil) | |
153 | assert f.valid_field_value?('') |
|
153 | assert f.valid_field_value?('') | |
154 | assert f.valid_field_value?('1975-07-14') |
|
154 | assert f.valid_field_value?('1975-07-14') | |
155 | assert !f.valid_field_value?('1975-07-33') |
|
155 | assert !f.valid_field_value?('1975-07-33') | |
156 | assert !f.valid_field_value?('abc') |
|
156 | assert !f.valid_field_value?('abc') | |
157 | end |
|
157 | end | |
158 |
|
158 | |||
159 | def test_list_field_validation |
|
159 | def test_list_field_validation | |
160 | f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) |
|
160 | f = CustomField.new(:field_format => 'list', :possible_values => ['value1', 'value2']) | |
161 |
|
161 | |||
162 | assert f.valid_field_value?(nil) |
|
162 | assert f.valid_field_value?(nil) | |
163 | assert f.valid_field_value?('') |
|
163 | assert f.valid_field_value?('') | |
164 | assert f.valid_field_value?('value2') |
|
164 | assert f.valid_field_value?('value2') | |
165 | assert !f.valid_field_value?('abc') |
|
165 | assert !f.valid_field_value?('abc') | |
166 | end |
|
166 | end | |
167 |
|
167 | |||
168 | def test_int_field_validation |
|
168 | def test_int_field_validation | |
169 | f = CustomField.new(:field_format => 'int') |
|
169 | f = CustomField.new(:field_format => 'int') | |
170 |
|
170 | |||
171 | assert f.valid_field_value?(nil) |
|
171 | assert f.valid_field_value?(nil) | |
172 | assert f.valid_field_value?('') |
|
172 | assert f.valid_field_value?('') | |
173 | assert f.valid_field_value?('123') |
|
173 | assert f.valid_field_value?('123') | |
174 | assert f.valid_field_value?('+123') |
|
174 | assert f.valid_field_value?('+123') | |
175 | assert f.valid_field_value?('-123') |
|
175 | assert f.valid_field_value?('-123') | |
176 | assert !f.valid_field_value?('6abc') |
|
176 | assert !f.valid_field_value?('6abc') | |
177 | end |
|
177 | end | |
178 |
|
178 | |||
179 | def test_float_field_validation |
|
179 | def test_float_field_validation | |
180 | f = CustomField.new(:field_format => 'float') |
|
180 | f = CustomField.new(:field_format => 'float') | |
181 |
|
181 | |||
182 | assert f.valid_field_value?(nil) |
|
182 | assert f.valid_field_value?(nil) | |
183 | assert f.valid_field_value?('') |
|
183 | assert f.valid_field_value?('') | |
184 | assert f.valid_field_value?('11.2') |
|
184 | assert f.valid_field_value?('11.2') | |
185 | assert f.valid_field_value?('-6.250') |
|
185 | assert f.valid_field_value?('-6.250') | |
186 | assert f.valid_field_value?('5') |
|
186 | assert f.valid_field_value?('5') | |
187 | assert !f.valid_field_value?('6abc') |
|
187 | assert !f.valid_field_value?('6abc') | |
188 | end |
|
188 | end | |
189 |
|
189 | |||
190 | def test_multi_field_validation |
|
190 | def test_multi_field_validation | |
191 | f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2']) |
|
191 | f = CustomField.new(:field_format => 'list', :multiple => 'true', :possible_values => ['value1', 'value2']) | |
192 |
|
192 | |||
193 | assert f.valid_field_value?(nil) |
|
193 | assert f.valid_field_value?(nil) | |
194 | assert f.valid_field_value?('') |
|
194 | assert f.valid_field_value?('') | |
195 | assert f.valid_field_value?([]) |
|
195 | assert f.valid_field_value?([]) | |
196 | assert f.valid_field_value?([nil]) |
|
196 | assert f.valid_field_value?([nil]) | |
197 | assert f.valid_field_value?(['']) |
|
197 | assert f.valid_field_value?(['']) | |
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?(['value2']) |
|
202 | assert f.valid_field_value?(['value2']) | |
203 | assert !f.valid_field_value?(['abc']) |
|
203 | assert !f.valid_field_value?(['abc']) | |
204 |
|
204 | |||
205 | assert f.valid_field_value?(['', 'value2']) |
|
205 | assert f.valid_field_value?(['', 'value2']) | |
206 | assert !f.valid_field_value?(['', 'abc']) |
|
206 | assert !f.valid_field_value?(['', 'abc']) | |
207 |
|
207 | |||
208 | assert f.valid_field_value?(['value1', 'value2']) |
|
208 | assert f.valid_field_value?(['value1', 'value2']) | |
209 | assert !f.valid_field_value?(['value1', 'abc']) |
|
209 | assert !f.valid_field_value?(['value1', 'abc']) | |
210 | end |
|
210 | end | |
211 |
|
211 | |||
212 | def test_value_class_should_return_the_class_used_for_fields_values |
|
212 | def test_value_class_should_return_the_class_used_for_fields_values | |
213 | assert_equal User, CustomField.new(:field_format => 'user').value_class |
|
213 | assert_equal User, CustomField.new(:field_format => 'user').value_class | |
214 | assert_equal Version, CustomField.new(:field_format => 'version').value_class |
|
214 | assert_equal Version, CustomField.new(:field_format => 'version').value_class | |
215 | end |
|
215 | end | |
216 |
|
216 | |||
217 | def test_value_class_should_return_nil_for_other_fields |
|
217 | def test_value_class_should_return_nil_for_other_fields | |
218 | assert_nil CustomField.new(:field_format => 'text').value_class |
|
218 | assert_nil CustomField.new(:field_format => 'text').value_class | |
219 | assert_nil CustomField.new.value_class |
|
219 | assert_nil CustomField.new.value_class | |
220 | end |
|
220 | end | |
|
221 | ||||
|
222 | def test_value_from_keyword_for_list_custom_field | |||
|
223 | field = CustomField.find(1) | |||
|
224 | assert_equal 'PostgreSQL', field.value_from_keyword('postgresql', Issue.find(1)) | |||
|
225 | end | |||
221 | end |
|
226 | end |
@@ -1,794 +1,794 | |||||
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 ' |
|
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 | str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..." |
|
455 | str = "Testmail from Webmail: \xc3\xa4 \xc3\xb6 \xc3\xbc..." | |
456 | str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) |
|
456 | str.force_encoding('UTF-8') if str.respond_to?(:force_encoding) | |
457 | assert_kind_of Issue, issue |
|
457 | assert_kind_of Issue, issue | |
458 | assert_equal str, issue.subject |
|
458 | assert_equal str, issue.subject | |
459 | end |
|
459 | end | |
460 |
|
460 | |||
461 | def test_add_issue_with_japanese_subject |
|
461 | def test_add_issue_with_japanese_subject | |
462 | issue = submit_email( |
|
462 | issue = submit_email( | |
463 | 'subject_japanese_1.eml', |
|
463 | 'subject_japanese_1.eml', | |
464 | :issue => {:project => 'ecookbook'} |
|
464 | :issue => {:project => 'ecookbook'} | |
465 | ) |
|
465 | ) | |
466 | assert_kind_of Issue, issue |
|
466 | assert_kind_of Issue, issue | |
467 | ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" |
|
467 | ja = "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" | |
468 | ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) |
|
468 | ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) | |
469 | assert_equal ja, issue.subject |
|
469 | assert_equal ja, issue.subject | |
470 | end |
|
470 | end | |
471 |
|
471 | |||
472 | def test_add_issue_with_no_subject_header |
|
472 | def test_add_issue_with_no_subject_header | |
473 | issue = submit_email( |
|
473 | issue = submit_email( | |
474 | 'no_subject_header.eml', |
|
474 | 'no_subject_header.eml', | |
475 | :issue => {:project => 'ecookbook'} |
|
475 | :issue => {:project => 'ecookbook'} | |
476 | ) |
|
476 | ) | |
477 | assert_kind_of Issue, issue |
|
477 | assert_kind_of Issue, issue | |
478 | assert_equal '(no subject)', issue.subject |
|
478 | assert_equal '(no subject)', issue.subject | |
479 | end |
|
479 | end | |
480 |
|
480 | |||
481 | def test_add_issue_with_mixed_japanese_subject |
|
481 | def test_add_issue_with_mixed_japanese_subject | |
482 | issue = submit_email( |
|
482 | issue = submit_email( | |
483 | 'subject_japanese_2.eml', |
|
483 | 'subject_japanese_2.eml', | |
484 | :issue => {:project => 'ecookbook'} |
|
484 | :issue => {:project => 'ecookbook'} | |
485 | ) |
|
485 | ) | |
486 | assert_kind_of Issue, issue |
|
486 | assert_kind_of Issue, issue | |
487 | ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" |
|
487 | ja = "Re: \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" | |
488 | ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) |
|
488 | ja.force_encoding('UTF-8') if ja.respond_to?(:force_encoding) | |
489 | assert_equal ja, issue.subject |
|
489 | assert_equal ja, issue.subject | |
490 | end |
|
490 | end | |
491 |
|
491 | |||
492 | def test_should_ignore_emails_from_locked_users |
|
492 | def test_should_ignore_emails_from_locked_users | |
493 | User.find(2).lock! |
|
493 | User.find(2).lock! | |
494 |
|
494 | |||
495 | MailHandler.any_instance.expects(:dispatch).never |
|
495 | MailHandler.any_instance.expects(:dispatch).never | |
496 | assert_no_difference 'Issue.count' do |
|
496 | assert_no_difference 'Issue.count' do | |
497 | assert_equal false, submit_email('ticket_on_given_project.eml') |
|
497 | assert_equal false, submit_email('ticket_on_given_project.eml') | |
498 | end |
|
498 | end | |
499 | end |
|
499 | end | |
500 |
|
500 | |||
501 | def test_should_ignore_emails_from_emission_address |
|
501 | def test_should_ignore_emails_from_emission_address | |
502 | Role.anonymous.add_permission!(:add_issues) |
|
502 | Role.anonymous.add_permission!(:add_issues) | |
503 | assert_no_difference 'User.count' do |
|
503 | assert_no_difference 'User.count' do | |
504 | assert_equal false, |
|
504 | assert_equal false, | |
505 | submit_email( |
|
505 | submit_email( | |
506 | 'ticket_from_emission_address.eml', |
|
506 | 'ticket_from_emission_address.eml', | |
507 | :issue => {:project => 'ecookbook'}, |
|
507 | :issue => {:project => 'ecookbook'}, | |
508 | :unknown_user => 'create' |
|
508 | :unknown_user => 'create' | |
509 | ) |
|
509 | ) | |
510 | end |
|
510 | end | |
511 | end |
|
511 | end | |
512 |
|
512 | |||
513 | def test_should_ignore_auto_replied_emails |
|
513 | def test_should_ignore_auto_replied_emails | |
514 | MailHandler.any_instance.expects(:dispatch).never |
|
514 | MailHandler.any_instance.expects(:dispatch).never | |
515 | [ |
|
515 | [ | |
516 | "X-Auto-Response-Suppress: OOF", |
|
516 | "X-Auto-Response-Suppress: OOF", | |
517 | "Auto-Submitted: auto-replied", |
|
517 | "Auto-Submitted: auto-replied", | |
518 | "Auto-Submitted: Auto-Replied", |
|
518 | "Auto-Submitted: Auto-Replied", | |
519 | "Auto-Submitted: auto-generated" |
|
519 | "Auto-Submitted: auto-generated" | |
520 | ].each do |header| |
|
520 | ].each do |header| | |
521 | raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) |
|
521 | raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml')) | |
522 | raw = header + "\n" + raw |
|
522 | raw = header + "\n" + raw | |
523 |
|
523 | |||
524 | assert_no_difference 'Issue.count' do |
|
524 | assert_no_difference 'Issue.count' do | |
525 | assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored" |
|
525 | assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored" | |
526 | end |
|
526 | end | |
527 | end |
|
527 | end | |
528 | end |
|
528 | end | |
529 |
|
529 | |||
530 | def test_add_issue_should_send_email_notification |
|
530 | def test_add_issue_should_send_email_notification | |
531 | Setting.notified_events = ['issue_added'] |
|
531 | Setting.notified_events = ['issue_added'] | |
532 | ActionMailer::Base.deliveries.clear |
|
532 | ActionMailer::Base.deliveries.clear | |
533 | # This email contains: 'Project: onlinestore' |
|
533 | # This email contains: 'Project: onlinestore' | |
534 | issue = submit_email('ticket_on_given_project.eml') |
|
534 | issue = submit_email('ticket_on_given_project.eml') | |
535 | assert issue.is_a?(Issue) |
|
535 | assert issue.is_a?(Issue) | |
536 | assert_equal 1, ActionMailer::Base.deliveries.size |
|
536 | assert_equal 1, ActionMailer::Base.deliveries.size | |
537 | end |
|
537 | end | |
538 |
|
538 | |||
539 | def test_update_issue |
|
539 | def test_update_issue | |
540 | journal = submit_email('ticket_reply.eml') |
|
540 | journal = submit_email('ticket_reply.eml') | |
541 | assert journal.is_a?(Journal) |
|
541 | assert journal.is_a?(Journal) | |
542 | assert_equal User.find_by_login('jsmith'), journal.user |
|
542 | assert_equal User.find_by_login('jsmith'), journal.user | |
543 | assert_equal Issue.find(2), journal.journalized |
|
543 | assert_equal Issue.find(2), journal.journalized | |
544 | assert_match /This is reply/, journal.notes |
|
544 | assert_match /This is reply/, journal.notes | |
545 | assert_equal false, journal.private_notes |
|
545 | assert_equal false, journal.private_notes | |
546 | assert_equal 'Feature request', journal.issue.tracker.name |
|
546 | assert_equal 'Feature request', journal.issue.tracker.name | |
547 | end |
|
547 | end | |
548 |
|
548 | |||
549 | def test_update_issue_with_attribute_changes |
|
549 | def test_update_issue_with_attribute_changes | |
550 | # This email contains: 'Status: Resolved' |
|
550 | # This email contains: 'Status: Resolved' | |
551 | journal = submit_email('ticket_reply_with_status.eml') |
|
551 | journal = submit_email('ticket_reply_with_status.eml') | |
552 | assert journal.is_a?(Journal) |
|
552 | assert journal.is_a?(Journal) | |
553 | issue = Issue.find(journal.issue.id) |
|
553 | issue = Issue.find(journal.issue.id) | |
554 | assert_equal User.find_by_login('jsmith'), journal.user |
|
554 | assert_equal User.find_by_login('jsmith'), journal.user | |
555 | assert_equal Issue.find(2), journal.journalized |
|
555 | assert_equal Issue.find(2), journal.journalized | |
556 | assert_match /This is reply/, journal.notes |
|
556 | assert_match /This is reply/, journal.notes | |
557 | assert_equal 'Feature request', journal.issue.tracker.name |
|
557 | assert_equal 'Feature request', journal.issue.tracker.name | |
558 | assert_equal IssueStatus.find_by_name("Resolved"), issue.status |
|
558 | assert_equal IssueStatus.find_by_name("Resolved"), issue.status | |
559 | assert_equal '2010-01-01', issue.start_date.to_s |
|
559 | assert_equal '2010-01-01', issue.start_date.to_s | |
560 | assert_equal '2010-12-31', issue.due_date.to_s |
|
560 | assert_equal '2010-12-31', issue.due_date.to_s | |
561 | assert_equal User.find_by_login('jsmith'), issue.assigned_to |
|
561 | assert_equal User.find_by_login('jsmith'), issue.assigned_to | |
562 | assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value |
|
562 | assert_equal "52.6", issue.custom_value_for(CustomField.find_by_name('Float field')).value | |
563 | # keywords should be removed from the email body |
|
563 | # keywords should be removed from the email body | |
564 | assert !journal.notes.match(/^Status:/i) |
|
564 | assert !journal.notes.match(/^Status:/i) | |
565 | assert !journal.notes.match(/^Start Date:/i) |
|
565 | assert !journal.notes.match(/^Start Date:/i) | |
566 | end |
|
566 | end | |
567 |
|
567 | |||
568 | def test_update_issue_with_attachment |
|
568 | def test_update_issue_with_attachment | |
569 | assert_difference 'Journal.count' do |
|
569 | assert_difference 'Journal.count' do | |
570 | assert_difference 'JournalDetail.count' do |
|
570 | assert_difference 'JournalDetail.count' do | |
571 | assert_difference 'Attachment.count' do |
|
571 | assert_difference 'Attachment.count' do | |
572 | assert_no_difference 'Issue.count' do |
|
572 | assert_no_difference 'Issue.count' do | |
573 | journal = submit_email('ticket_with_attachment.eml') do |raw| |
|
573 | journal = submit_email('ticket_with_attachment.eml') do |raw| | |
574 | raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' |
|
574 | raw.gsub! /^Subject: .*$/, 'Subject: Re: [Cookbook - Feature #2] (New) Add ingredients categories' | |
575 | end |
|
575 | end | |
576 | end |
|
576 | end | |
577 | end |
|
577 | end | |
578 | end |
|
578 | end | |
579 | end |
|
579 | end | |
580 | journal = Journal.first(:order => 'id DESC') |
|
580 | journal = Journal.first(:order => 'id DESC') | |
581 | assert_equal Issue.find(2), journal.journalized |
|
581 | assert_equal Issue.find(2), journal.journalized | |
582 | assert_equal 1, journal.details.size |
|
582 | assert_equal 1, journal.details.size | |
583 |
|
583 | |||
584 | detail = journal.details.first |
|
584 | detail = journal.details.first | |
585 | assert_equal 'attachment', detail.property |
|
585 | assert_equal 'attachment', detail.property | |
586 | assert_equal 'Paella.jpg', detail.value |
|
586 | assert_equal 'Paella.jpg', detail.value | |
587 | end |
|
587 | end | |
588 |
|
588 | |||
589 | def test_update_issue_should_send_email_notification |
|
589 | def test_update_issue_should_send_email_notification | |
590 | ActionMailer::Base.deliveries.clear |
|
590 | ActionMailer::Base.deliveries.clear | |
591 | journal = submit_email('ticket_reply.eml') |
|
591 | journal = submit_email('ticket_reply.eml') | |
592 | assert journal.is_a?(Journal) |
|
592 | assert journal.is_a?(Journal) | |
593 | assert_equal 1, ActionMailer::Base.deliveries.size |
|
593 | assert_equal 1, ActionMailer::Base.deliveries.size | |
594 | end |
|
594 | end | |
595 |
|
595 | |||
596 | def test_update_issue_should_not_set_defaults |
|
596 | def test_update_issue_should_not_set_defaults | |
597 | journal = submit_email( |
|
597 | journal = submit_email( | |
598 | 'ticket_reply.eml', |
|
598 | 'ticket_reply.eml', | |
599 | :issue => {:tracker => 'Support request', :priority => 'High'} |
|
599 | :issue => {:tracker => 'Support request', :priority => 'High'} | |
600 | ) |
|
600 | ) | |
601 | assert journal.is_a?(Journal) |
|
601 | assert journal.is_a?(Journal) | |
602 | assert_match /This is reply/, journal.notes |
|
602 | assert_match /This is reply/, journal.notes | |
603 | assert_equal 'Feature request', journal.issue.tracker.name |
|
603 | assert_equal 'Feature request', journal.issue.tracker.name | |
604 | assert_equal 'Normal', journal.issue.priority.name |
|
604 | assert_equal 'Normal', journal.issue.priority.name | |
605 | end |
|
605 | end | |
606 |
|
606 | |||
607 | def test_replying_to_a_private_note_should_add_reply_as_private |
|
607 | def test_replying_to_a_private_note_should_add_reply_as_private | |
608 | private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2) |
|
608 | private_journal = Journal.create!(:notes => 'Private notes', :journalized => Issue.find(1), :private_notes => true, :user_id => 2) | |
609 |
|
609 | |||
610 | assert_difference 'Journal.count' do |
|
610 | assert_difference 'Journal.count' do | |
611 | journal = submit_email('ticket_reply.eml') do |email| |
|
611 | journal = submit_email('ticket_reply.eml') do |email| | |
612 | email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>" |
|
612 | email.sub! %r{^In-Reply-To:.*$}, "In-Reply-To: <redmine.journal-#{private_journal.id}.20060719210421@osiris>" | |
613 | end |
|
613 | end | |
614 |
|
614 | |||
615 | assert_kind_of Journal, journal |
|
615 | assert_kind_of Journal, journal | |
616 | assert_match /This is reply/, journal.notes |
|
616 | assert_match /This is reply/, journal.notes | |
617 | assert_equal true, journal.private_notes |
|
617 | assert_equal true, journal.private_notes | |
618 | end |
|
618 | end | |
619 | end |
|
619 | end | |
620 |
|
620 | |||
621 | def test_reply_to_a_message |
|
621 | def test_reply_to_a_message | |
622 | m = submit_email('message_reply.eml') |
|
622 | m = submit_email('message_reply.eml') | |
623 | assert m.is_a?(Message) |
|
623 | assert m.is_a?(Message) | |
624 | assert !m.new_record? |
|
624 | assert !m.new_record? | |
625 | m.reload |
|
625 | m.reload | |
626 | assert_equal 'Reply via email', m.subject |
|
626 | assert_equal 'Reply via email', m.subject | |
627 | # The email replies to message #2 which is part of the thread of message #1 |
|
627 | # The email replies to message #2 which is part of the thread of message #1 | |
628 | assert_equal Message.find(1), m.parent |
|
628 | assert_equal Message.find(1), m.parent | |
629 | end |
|
629 | end | |
630 |
|
630 | |||
631 | def test_reply_to_a_message_by_subject |
|
631 | def test_reply_to_a_message_by_subject | |
632 | m = submit_email('message_reply_by_subject.eml') |
|
632 | m = submit_email('message_reply_by_subject.eml') | |
633 | assert m.is_a?(Message) |
|
633 | assert m.is_a?(Message) | |
634 | assert !m.new_record? |
|
634 | assert !m.new_record? | |
635 | m.reload |
|
635 | m.reload | |
636 | assert_equal 'Reply to the first post', m.subject |
|
636 | assert_equal 'Reply to the first post', m.subject | |
637 | assert_equal Message.find(1), m.parent |
|
637 | assert_equal Message.find(1), m.parent | |
638 | end |
|
638 | end | |
639 |
|
639 | |||
640 | def test_should_strip_tags_of_html_only_emails |
|
640 | def test_should_strip_tags_of_html_only_emails | |
641 | issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'}) |
|
641 | issue = submit_email('ticket_html_only.eml', :issue => {:project => 'ecookbook'}) | |
642 | assert issue.is_a?(Issue) |
|
642 | assert issue.is_a?(Issue) | |
643 | assert !issue.new_record? |
|
643 | assert !issue.new_record? | |
644 | issue.reload |
|
644 | issue.reload | |
645 | assert_equal 'HTML email', issue.subject |
|
645 | assert_equal 'HTML email', issue.subject | |
646 | assert_equal 'This is a html-only email.', issue.description |
|
646 | assert_equal 'This is a html-only email.', issue.description | |
647 | end |
|
647 | end | |
648 |
|
648 | |||
649 | context "truncate emails based on the Setting" do |
|
649 | context "truncate emails based on the Setting" do | |
650 | context "with no setting" do |
|
650 | context "with no setting" do | |
651 | setup do |
|
651 | setup do | |
652 | Setting.mail_handler_body_delimiters = '' |
|
652 | Setting.mail_handler_body_delimiters = '' | |
653 | end |
|
653 | end | |
654 |
|
654 | |||
655 | should "add the entire email into the issue" do |
|
655 | should "add the entire email into the issue" do | |
656 | issue = submit_email('ticket_on_given_project.eml') |
|
656 | issue = submit_email('ticket_on_given_project.eml') | |
657 | assert_issue_created(issue) |
|
657 | assert_issue_created(issue) | |
658 | assert issue.description.include?('---') |
|
658 | assert issue.description.include?('---') | |
659 | assert issue.description.include?('This paragraph is after the delimiter') |
|
659 | assert issue.description.include?('This paragraph is after the delimiter') | |
660 | end |
|
660 | end | |
661 | end |
|
661 | end | |
662 |
|
662 | |||
663 | context "with a single string" do |
|
663 | context "with a single string" do | |
664 | setup do |
|
664 | setup do | |
665 | Setting.mail_handler_body_delimiters = '---' |
|
665 | Setting.mail_handler_body_delimiters = '---' | |
666 | end |
|
666 | end | |
667 | should "truncate the email at the delimiter for the issue" do |
|
667 | should "truncate the email at the delimiter for the issue" do | |
668 | issue = submit_email('ticket_on_given_project.eml') |
|
668 | issue = submit_email('ticket_on_given_project.eml') | |
669 | assert_issue_created(issue) |
|
669 | assert_issue_created(issue) | |
670 | assert issue.description.include?('This paragraph is before delimiters') |
|
670 | assert issue.description.include?('This paragraph is before delimiters') | |
671 | assert issue.description.include?('--- This line starts with a delimiter') |
|
671 | assert issue.description.include?('--- This line starts with a delimiter') | |
672 | assert !issue.description.match(/^---$/) |
|
672 | assert !issue.description.match(/^---$/) | |
673 | assert !issue.description.include?('This paragraph is after the delimiter') |
|
673 | assert !issue.description.include?('This paragraph is after the delimiter') | |
674 | end |
|
674 | end | |
675 | end |
|
675 | end | |
676 |
|
676 | |||
677 | context "with a single quoted reply (e.g. reply to a Redmine email notification)" do |
|
677 | context "with a single quoted reply (e.g. reply to a Redmine email notification)" do | |
678 | setup do |
|
678 | setup do | |
679 | Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' |
|
679 | Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' | |
680 | end |
|
680 | end | |
681 | should "truncate the email at the delimiter with the quoted reply symbols (>)" do |
|
681 | should "truncate the email at the delimiter with the quoted reply symbols (>)" do | |
682 | journal = submit_email('issue_update_with_quoted_reply_above.eml') |
|
682 | journal = submit_email('issue_update_with_quoted_reply_above.eml') | |
683 | assert journal.is_a?(Journal) |
|
683 | assert journal.is_a?(Journal) | |
684 | assert journal.notes.include?('An update to the issue by the sender.') |
|
684 | assert journal.notes.include?('An update to the issue by the sender.') | |
685 | assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) |
|
685 | assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) | |
686 | assert !journal.notes.include?('Looks like the JSON api for projects was missed.') |
|
686 | assert !journal.notes.include?('Looks like the JSON api for projects was missed.') | |
687 | end |
|
687 | end | |
688 | end |
|
688 | end | |
689 |
|
689 | |||
690 | context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do |
|
690 | context "with multiple quoted replies (e.g. reply to a reply of a Redmine email notification)" do | |
691 | setup do |
|
691 | setup do | |
692 | Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' |
|
692 | Setting.mail_handler_body_delimiters = '--- Reply above. Do not remove this line. ---' | |
693 | end |
|
693 | end | |
694 | should "truncate the email at the delimiter with the quoted reply symbols (>)" do |
|
694 | should "truncate the email at the delimiter with the quoted reply symbols (>)" do | |
695 | journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml') |
|
695 | journal = submit_email('issue_update_with_multiple_quoted_reply_above.eml') | |
696 | assert journal.is_a?(Journal) |
|
696 | assert journal.is_a?(Journal) | |
697 | assert journal.notes.include?('An update to the issue by the sender.') |
|
697 | assert journal.notes.include?('An update to the issue by the sender.') | |
698 | assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) |
|
698 | assert !journal.notes.match(Regexp.escape("--- Reply above. Do not remove this line. ---")) | |
699 | assert !journal.notes.include?('Looks like the JSON api for projects was missed.') |
|
699 | assert !journal.notes.include?('Looks like the JSON api for projects was missed.') | |
700 | end |
|
700 | end | |
701 | end |
|
701 | end | |
702 |
|
702 | |||
703 | context "with multiple strings" do |
|
703 | context "with multiple strings" do | |
704 | setup do |
|
704 | setup do | |
705 | Setting.mail_handler_body_delimiters = "---\nBREAK" |
|
705 | Setting.mail_handler_body_delimiters = "---\nBREAK" | |
706 | end |
|
706 | end | |
707 | should "truncate the email at the first delimiter found (BREAK)" do |
|
707 | should "truncate the email at the first delimiter found (BREAK)" do | |
708 | issue = submit_email('ticket_on_given_project.eml') |
|
708 | issue = submit_email('ticket_on_given_project.eml') | |
709 | assert_issue_created(issue) |
|
709 | assert_issue_created(issue) | |
710 | assert issue.description.include?('This paragraph is before delimiters') |
|
710 | assert issue.description.include?('This paragraph is before delimiters') | |
711 | assert !issue.description.include?('BREAK') |
|
711 | assert !issue.description.include?('BREAK') | |
712 | assert !issue.description.include?('This paragraph is between delimiters') |
|
712 | assert !issue.description.include?('This paragraph is between delimiters') | |
713 | assert !issue.description.match(/^---$/) |
|
713 | assert !issue.description.match(/^---$/) | |
714 | assert !issue.description.include?('This paragraph is after the delimiter') |
|
714 | assert !issue.description.include?('This paragraph is after the delimiter') | |
715 | end |
|
715 | end | |
716 | end |
|
716 | end | |
717 | end |
|
717 | end | |
718 |
|
718 | |||
719 | def test_email_with_long_subject_line |
|
719 | def test_email_with_long_subject_line | |
720 | issue = submit_email('ticket_with_long_subject.eml') |
|
720 | issue = submit_email('ticket_with_long_subject.eml') | |
721 | assert issue.is_a?(Issue) |
|
721 | assert issue.is_a?(Issue) | |
722 | 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] |
|
722 | 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] | |
723 | end |
|
723 | end | |
724 |
|
724 | |||
725 | def test_new_user_from_attributes_should_return_valid_user |
|
725 | def test_new_user_from_attributes_should_return_valid_user | |
726 | to_test = { |
|
726 | to_test = { | |
727 | # [address, name] => [login, firstname, lastname] |
|
727 | # [address, name] => [login, firstname, lastname] | |
728 | ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'], |
|
728 | ['jsmith@example.net', nil] => ['jsmith@example.net', 'jsmith', '-'], | |
729 | ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'], |
|
729 | ['jsmith@example.net', 'John'] => ['jsmith@example.net', 'John', '-'], | |
730 | ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'], |
|
730 | ['jsmith@example.net', 'John Smith'] => ['jsmith@example.net', 'John', 'Smith'], | |
731 | ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'], |
|
731 | ['jsmith@example.net', 'John Paul Smith'] => ['jsmith@example.net', 'John', 'Paul Smith'], | |
732 | ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'], |
|
732 | ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsTheMaximumLength Smith'] => ['jsmith@example.net', 'AVeryLongFirstnameThatExceedsT', 'Smith'], | |
733 | ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh'] |
|
733 | ['jsmith@example.net', 'John AVeryLongLastnameThatExceedsTheMaximumLength'] => ['jsmith@example.net', 'John', 'AVeryLongLastnameThatExceedsTh'] | |
734 | } |
|
734 | } | |
735 |
|
735 | |||
736 | to_test.each do |attrs, expected| |
|
736 | to_test.each do |attrs, expected| | |
737 | user = MailHandler.new_user_from_attributes(attrs.first, attrs.last) |
|
737 | user = MailHandler.new_user_from_attributes(attrs.first, attrs.last) | |
738 |
|
738 | |||
739 | assert user.valid?, user.errors.full_messages.to_s |
|
739 | assert user.valid?, user.errors.full_messages.to_s | |
740 | assert_equal attrs.first, user.mail |
|
740 | assert_equal attrs.first, user.mail | |
741 | assert_equal expected[0], user.login |
|
741 | assert_equal expected[0], user.login | |
742 | assert_equal expected[1], user.firstname |
|
742 | assert_equal expected[1], user.firstname | |
743 | assert_equal expected[2], user.lastname |
|
743 | assert_equal expected[2], user.lastname | |
744 | end |
|
744 | end | |
745 | end |
|
745 | end | |
746 |
|
746 | |||
747 | def test_new_user_from_attributes_should_respect_minimum_password_length |
|
747 | def test_new_user_from_attributes_should_respect_minimum_password_length | |
748 | with_settings :password_min_length => 15 do |
|
748 | with_settings :password_min_length => 15 do | |
749 | user = MailHandler.new_user_from_attributes('jsmith@example.net') |
|
749 | user = MailHandler.new_user_from_attributes('jsmith@example.net') | |
750 | assert user.valid? |
|
750 | assert user.valid? | |
751 | assert user.password.length >= 15 |
|
751 | assert user.password.length >= 15 | |
752 | end |
|
752 | end | |
753 | end |
|
753 | end | |
754 |
|
754 | |||
755 | def test_new_user_from_attributes_should_use_default_login_if_invalid |
|
755 | def test_new_user_from_attributes_should_use_default_login_if_invalid | |
756 | user = MailHandler.new_user_from_attributes('foo+bar@example.net') |
|
756 | user = MailHandler.new_user_from_attributes('foo+bar@example.net') | |
757 | assert user.valid? |
|
757 | assert user.valid? | |
758 | assert user.login =~ /^user[a-f0-9]+$/ |
|
758 | assert user.login =~ /^user[a-f0-9]+$/ | |
759 | assert_equal 'foo+bar@example.net', user.mail |
|
759 | assert_equal 'foo+bar@example.net', user.mail | |
760 | end |
|
760 | end | |
761 |
|
761 | |||
762 | def test_new_user_with_utf8_encoded_fullname_should_be_decoded |
|
762 | def test_new_user_with_utf8_encoded_fullname_should_be_decoded | |
763 | assert_difference 'User.count' do |
|
763 | assert_difference 'User.count' do | |
764 | issue = submit_email( |
|
764 | issue = submit_email( | |
765 | 'fullname_of_sender_as_utf8_encoded.eml', |
|
765 | 'fullname_of_sender_as_utf8_encoded.eml', | |
766 | :issue => {:project => 'ecookbook'}, |
|
766 | :issue => {:project => 'ecookbook'}, | |
767 | :unknown_user => 'create' |
|
767 | :unknown_user => 'create' | |
768 | ) |
|
768 | ) | |
769 | end |
|
769 | end | |
770 |
|
770 | |||
771 | user = User.first(:order => 'id DESC') |
|
771 | user = User.first(:order => 'id DESC') | |
772 | assert_equal "foo@example.org", user.mail |
|
772 | assert_equal "foo@example.org", user.mail | |
773 | str1 = "\xc3\x84\xc3\xa4" |
|
773 | str1 = "\xc3\x84\xc3\xa4" | |
774 | str2 = "\xc3\x96\xc3\xb6" |
|
774 | str2 = "\xc3\x96\xc3\xb6" | |
775 | str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) |
|
775 | str1.force_encoding('UTF-8') if str1.respond_to?(:force_encoding) | |
776 | str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding) |
|
776 | str2.force_encoding('UTF-8') if str2.respond_to?(:force_encoding) | |
777 | assert_equal str1, user.firstname |
|
777 | assert_equal str1, user.firstname | |
778 | assert_equal str2, user.lastname |
|
778 | assert_equal str2, user.lastname | |
779 | end |
|
779 | end | |
780 |
|
780 | |||
781 | private |
|
781 | private | |
782 |
|
782 | |||
783 | def submit_email(filename, options={}) |
|
783 | def submit_email(filename, options={}) | |
784 | raw = IO.read(File.join(FIXTURES_PATH, filename)) |
|
784 | raw = IO.read(File.join(FIXTURES_PATH, filename)) | |
785 | yield raw if block_given? |
|
785 | yield raw if block_given? | |
786 | MailHandler.receive(raw, options) |
|
786 | MailHandler.receive(raw, options) | |
787 | end |
|
787 | end | |
788 |
|
788 | |||
789 | def assert_issue_created(issue) |
|
789 | def assert_issue_created(issue) | |
790 | assert issue.is_a?(Issue) |
|
790 | assert issue.is_a?(Issue) | |
791 | assert !issue.new_record? |
|
791 | assert !issue.new_record? | |
792 | issue.reload |
|
792 | issue.reload | |
793 | end |
|
793 | end | |
794 | end |
|
794 | end |
General Comments 0
You need to be logged in to leave comments.
Login now