##// END OF EJS Templates
New custom fields of existing issues are not initialized with their default value (#21074)....
Jean-Philippe Lang -
r14391:e6112a9710cb
parent child
Show More
@@ -1,50 +1,50
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class CustomValue < ActiveRecord::Base
19 19 belongs_to :custom_field
20 20 belongs_to :customized, :polymorphic => true
21 21 attr_protected :id
22 22
23 23 def initialize(attributes=nil, *args)
24 24 super
25 if new_record? && custom_field && (customized_type.blank? || (customized && customized.new_record?))
25 if new_record? && custom_field && !attributes.key?(:value)
26 26 self.value ||= custom_field.default_value
27 27 end
28 28 end
29 29
30 30 # Returns true if the boolean custom value is true
31 31 def true?
32 32 self.value == '1'
33 33 end
34 34
35 35 def editable?
36 36 custom_field.editable?
37 37 end
38 38
39 39 def visible?
40 40 custom_field.visible?
41 41 end
42 42
43 43 def required?
44 44 custom_field.is_required?
45 45 end
46 46
47 47 def to_s
48 48 value.to_s
49 49 end
50 50 end
@@ -1,170 +1,170
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Acts
20 20 module Customizable
21 21 def self.included(base)
22 22 base.extend ClassMethods
23 23 end
24 24
25 25 module ClassMethods
26 26 def acts_as_customizable(options = {})
27 27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
28 28 cattr_accessor :customizable_options
29 29 self.customizable_options = options
30 30 has_many :custom_values, lambda {includes(:custom_field).order("#{CustomField.table_name}.position")},
31 31 :as => :customized,
32 32 :dependent => :delete_all,
33 33 :validate => false
34 34
35 35 send :include, Redmine::Acts::Customizable::InstanceMethods
36 36 validate :validate_custom_field_values
37 37 after_save :save_custom_field_values
38 38 end
39 39 end
40 40
41 41 module InstanceMethods
42 42 def self.included(base)
43 43 base.extend ClassMethods
44 44 base.send :alias_method_chain, :reload, :custom_fields
45 45 end
46 46
47 47 def available_custom_fields
48 48 CustomField.where("type = '#{self.class.name}CustomField'").sorted.to_a
49 49 end
50 50
51 51 # Sets the values of the object's custom fields
52 52 # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}]
53 53 def custom_fields=(values)
54 54 values_to_hash = values.inject({}) do |hash, v|
55 55 v = v.stringify_keys
56 56 if v['id'] && v.has_key?('value')
57 57 hash[v['id']] = v['value']
58 58 end
59 59 hash
60 60 end
61 61 self.custom_field_values = values_to_hash
62 62 end
63 63
64 64 # Sets the values of the object's custom fields
65 65 # values is a hash like {'1' => 'foo', 2 => 'bar'}
66 66 def custom_field_values=(values)
67 67 values = values.stringify_keys
68 68
69 69 custom_field_values.each do |custom_field_value|
70 70 key = custom_field_value.custom_field_id.to_s
71 71 if values.has_key?(key)
72 72 value = values[key]
73 73 if value.is_a?(Array)
74 74 value = value.reject(&:blank?).map(&:to_s).uniq
75 75 if value.empty?
76 76 value << ''
77 77 end
78 78 else
79 79 value = value.to_s
80 80 end
81 81 custom_field_value.value = value
82 82 end
83 83 end
84 84 @custom_field_values_changed = true
85 85 end
86 86
87 87 def custom_field_values
88 88 @custom_field_values ||= available_custom_fields.collect do |field|
89 89 x = CustomFieldValue.new
90 90 x.custom_field = field
91 91 x.customized = self
92 92 if field.multiple?
93 93 values = custom_values.select { |v| v.custom_field == field }
94 94 if values.empty?
95 values << custom_values.build(:customized => self, :custom_field => field, :value => nil)
95 values << custom_values.build(:customized => self, :custom_field => field)
96 96 end
97 97 x.value = values.map(&:value)
98 98 else
99 99 cv = custom_values.detect { |v| v.custom_field == field }
100 cv ||= custom_values.build(:customized => self, :custom_field => field, :value => nil)
100 cv ||= custom_values.build(:customized => self, :custom_field => field)
101 101 x.value = cv.value
102 102 end
103 103 x.value_was = x.value.dup if x.value
104 104 x
105 105 end
106 106 end
107 107
108 108 def visible_custom_field_values
109 109 custom_field_values.select(&:visible?)
110 110 end
111 111
112 112 def custom_field_values_changed?
113 113 @custom_field_values_changed == true
114 114 end
115 115
116 116 def custom_value_for(c)
117 117 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
118 118 custom_values.detect {|v| v.custom_field_id == field_id }
119 119 end
120 120
121 121 def custom_field_value(c)
122 122 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
123 123 custom_field_values.detect {|v| v.custom_field_id == field_id }.try(:value)
124 124 end
125 125
126 126 def validate_custom_field_values
127 127 if new_record? || custom_field_values_changed?
128 128 custom_field_values.each(&:validate_value)
129 129 end
130 130 end
131 131
132 132 def save_custom_field_values
133 133 target_custom_values = []
134 134 custom_field_values.each do |custom_field_value|
135 135 if custom_field_value.value.is_a?(Array)
136 136 custom_field_value.value.each do |v|
137 137 target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field && cv.value == v}
138 138 target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field, :value => v)
139 139 target_custom_values << target
140 140 end
141 141 else
142 142 target = custom_values.detect {|cv| cv.custom_field == custom_field_value.custom_field}
143 143 target ||= custom_values.build(:customized => self, :custom_field => custom_field_value.custom_field)
144 144 target.value = custom_field_value.value
145 145 target_custom_values << target
146 146 end
147 147 end
148 148 self.custom_values = target_custom_values
149 149 custom_values.each(&:save)
150 150 @custom_field_values_changed = false
151 151 true
152 152 end
153 153
154 154 def reset_custom_values!
155 155 @custom_field_values = nil
156 156 @custom_field_values_changed = true
157 157 end
158 158
159 159 def reload_with_custom_fields(*args)
160 160 @custom_field_values = nil
161 161 @custom_field_values_changed = false
162 162 reload_without_custom_fields(*args)
163 163 end
164 164
165 165 module ClassMethods
166 166 end
167 167 end
168 168 end
169 169 end
170 170 end
@@ -1,782 +1,782
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../test_helper', __FILE__)
19 19
20 20 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
21 21 fixtures :projects,
22 22 :users,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_relations,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries,
45 45 :attachments
46 46
47 47 test "GET /issues.xml should contain metadata" do
48 48 get '/issues.xml'
49 49 assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]',
50 50 assigns(:issue_count).to_s
51 51 end
52 52
53 53 test "GET /issues.xml with nometa param should not contain metadata" do
54 54 get '/issues.xml?nometa=1'
55 55 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
56 56 end
57 57
58 58 test "GET /issues.xml with nometa header should not contain metadata" do
59 59 get '/issues.xml', {}, {'X-Redmine-Nometa' => '1'}
60 60 assert_select 'issues[type=array]:not([total_count]):not([limit]):not([offset])'
61 61 end
62 62
63 63 test "GET /issues.xml with offset and limit" do
64 64 get '/issues.xml?offset=2&limit=3'
65 65
66 66 assert_equal 3, assigns(:limit)
67 67 assert_equal 2, assigns(:offset)
68 68 assert_select 'issues issue', 3
69 69 end
70 70
71 71 test "GET /issues.xml with relations" do
72 72 get '/issues.xml?include=relations'
73 73
74 74 assert_response :success
75 75 assert_equal 'application/xml', @response.content_type
76 76
77 77 assert_select 'issue id', :text => '3' do
78 78 assert_select '~ relations relation', 1
79 79 assert_select '~ relations relation[id="2"][issue_id="2"][issue_to_id="3"][relation_type=relates]'
80 80 end
81 81
82 82 assert_select 'issue id', :text => '1' do
83 83 assert_select '~ relations'
84 84 assert_select '~ relations relation', 0
85 85 end
86 86 end
87 87
88 88 test "GET /issues.xml with invalid query params" do
89 89 get '/issues.xml', {:f => ['start_date'], :op => {:start_date => '='}}
90 90
91 91 assert_response :unprocessable_entity
92 92 assert_equal 'application/xml', @response.content_type
93 93 assert_select 'errors error', :text => "Start date cannot be blank"
94 94 end
95 95
96 96 test "GET /issues.xml with custom field filter" do
97 97 get '/issues.xml',
98 98 {:set_filter => 1, :f => ['cf_1'], :op => {:cf_1 => '='}, :v => {:cf_1 => ['MySQL']}}
99 99
100 100 expected_ids = Issue.visible.
101 101 joins(:custom_values).
102 102 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
103 103 assert expected_ids.any?
104 104
105 105 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
106 106 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
107 107 end
108 108 end
109 109
110 110 test "GET /issues.xml with custom field filter (shorthand method)" do
111 111 get '/issues.xml', {:cf_1 => 'MySQL'}
112 112
113 113 expected_ids = Issue.visible.
114 114 joins(:custom_values).
115 115 where(:custom_values => {:custom_field_id => 1, :value => 'MySQL'}).map(&:id)
116 116 assert expected_ids.any?
117 117
118 118 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
119 119 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
120 120 end
121 121 end
122 122
123 123 def test_index_should_include_issue_attributes
124 124 get '/issues.xml'
125 125 assert_select 'issues>issue>is_private', :text => 'false'
126 126 end
127 127
128 128 def test_index_should_allow_timestamp_filtering
129 129 Issue.delete_all
130 130 Issue.generate!(:subject => '1').update_column(:updated_on, Time.parse("2014-01-02T10:25:00Z"))
131 131 Issue.generate!(:subject => '2').update_column(:updated_on, Time.parse("2014-01-02T12:13:00Z"))
132 132
133 133 get '/issues.xml',
134 134 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '<='},
135 135 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
136 136 assert_select 'issues>issue', :count => 1
137 137 assert_select 'issues>issue>subject', :text => '1'
138 138
139 139 get '/issues.xml',
140 140 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
141 141 :v => {:updated_on => ['2014-01-02T12:00:00Z']}}
142 142 assert_select 'issues>issue', :count => 1
143 143 assert_select 'issues>issue>subject', :text => '2'
144 144
145 145 get '/issues.xml',
146 146 {:set_filter => 1, :f => ['updated_on'], :op => {:updated_on => '>='},
147 147 :v => {:updated_on => ['2014-01-02T08:00:00Z']}}
148 148 assert_select 'issues>issue', :count => 2
149 149 end
150 150
151 151 test "GET /issues.xml with filter" do
152 152 get '/issues.xml?status_id=5'
153 153
154 154 expected_ids = Issue.visible.where(:status_id => 5).map(&:id)
155 155 assert expected_ids.any?
156 156
157 157 assert_select 'issues > issue > id', :count => expected_ids.count do |ids|
158 158 ids.each { |id| assert expected_ids.delete(id.children.first.content.to_i) }
159 159 end
160 160 end
161 161
162 162 test "GET /issues.json with filter" do
163 163 get '/issues.json?status_id=5'
164 164
165 165 json = ActiveSupport::JSON.decode(response.body)
166 166 status_ids_used = json['issues'].collect {|j| j['status']['id'] }
167 167 assert_equal 3, status_ids_used.length
168 168 assert status_ids_used.all? {|id| id == 5 }
169 169 end
170 170
171 171 test "GET /issues/:id.xml with journals" do
172 172 get '/issues/1.xml?include=journals'
173 173
174 174 assert_select 'issue journals[type=array]' do
175 175 assert_select 'journal[id="1"]' do
176 176 assert_select 'details[type=array]' do
177 177 assert_select 'detail[name=status_id]' do
178 178 assert_select 'old_value', :text => '1'
179 179 assert_select 'new_value', :text => '2'
180 180 end
181 181 end
182 182 end
183 183 end
184 184 end
185 185
186 186 test "GET /issues/:id.xml with journals should format timestamps in ISO 8601" do
187 187 get '/issues/1.xml?include=journals'
188 188
189 189 iso_date = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/
190 190 assert_select 'issue>created_on', :text => iso_date
191 191 assert_select 'issue>updated_on', :text => iso_date
192 192 assert_select 'issue journal>created_on', :text => iso_date
193 193 end
194 194
195 195 test "GET /issues/:id.xml with custom fields" do
196 196 get '/issues/3.xml'
197 197
198 198 assert_select 'issue custom_fields[type=array]' do
199 199 assert_select 'custom_field[id="1"]' do
200 200 assert_select 'value', :text => 'MySQL'
201 201 end
202 202 end
203 203 assert_nothing_raised do
204 204 Hash.from_xml(response.body).to_xml
205 205 end
206 206 end
207 207
208 208 test "GET /issues/:id.xml with multi custom fields" do
209 209 field = CustomField.find(1)
210 210 field.update_attribute :multiple, true
211 211 issue = Issue.find(3)
212 212 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
213 213 issue.save!
214 214
215 215 get '/issues/3.xml'
216 216 assert_response :success
217 217
218 218 assert_select 'issue custom_fields[type=array]' do
219 219 assert_select 'custom_field[id="1"]' do
220 220 assert_select 'value[type=array] value', 2
221 221 end
222 222 end
223 223 xml = Hash.from_xml(response.body)
224 224 custom_fields = xml['issue']['custom_fields']
225 225 assert_kind_of Array, custom_fields
226 226 field = custom_fields.detect {|f| f['id'] == '1'}
227 227 assert_kind_of Hash, field
228 228 assert_equal ['MySQL', 'Oracle'], field['value'].sort
229 229 end
230 230
231 231 test "GET /issues/:id.json with multi custom fields" do
232 232 field = CustomField.find(1)
233 233 field.update_attribute :multiple, true
234 234 issue = Issue.find(3)
235 235 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
236 236 issue.save!
237 237
238 238 get '/issues/3.json'
239 239 assert_response :success
240 240
241 241 json = ActiveSupport::JSON.decode(response.body)
242 242 custom_fields = json['issue']['custom_fields']
243 243 assert_kind_of Array, custom_fields
244 244 field = custom_fields.detect {|f| f['id'] == 1}
245 245 assert_kind_of Hash, field
246 246 assert_equal ['MySQL', 'Oracle'], field['value'].sort
247 247 end
248 248
249 249 test "GET /issues/:id.xml with empty value for multi custom field" do
250 250 field = CustomField.find(1)
251 251 field.update_attribute :multiple, true
252 252 issue = Issue.find(3)
253 253 issue.custom_field_values = {1 => ['']}
254 254 issue.save!
255 255
256 256 get '/issues/3.xml'
257 257
258 258 assert_select 'issue custom_fields[type=array]' do
259 259 assert_select 'custom_field[id="1"]' do
260 260 assert_select 'value[type=array]:empty'
261 261 end
262 262 end
263 263 xml = Hash.from_xml(response.body)
264 264 custom_fields = xml['issue']['custom_fields']
265 265 assert_kind_of Array, custom_fields
266 266 field = custom_fields.detect {|f| f['id'] == '1'}
267 267 assert_kind_of Hash, field
268 268 assert_equal [], field['value']
269 269 end
270 270
271 271 test "GET /issues/:id.json with empty value for multi custom field" do
272 272 field = CustomField.find(1)
273 273 field.update_attribute :multiple, true
274 274 issue = Issue.find(3)
275 275 issue.custom_field_values = {1 => ['']}
276 276 issue.save!
277 277
278 278 get '/issues/3.json'
279 279 assert_response :success
280 280 json = ActiveSupport::JSON.decode(response.body)
281 281 custom_fields = json['issue']['custom_fields']
282 282 assert_kind_of Array, custom_fields
283 283 field = custom_fields.detect {|f| f['id'] == 1}
284 284 assert_kind_of Hash, field
285 285 assert_equal [], field['value'].sort
286 286 end
287 287
288 288 test "GET /issues/:id.xml with attachments" do
289 289 get '/issues/3.xml?include=attachments'
290 290
291 291 assert_select 'issue attachments[type=array]' do
292 292 assert_select 'attachment', 4
293 293 assert_select 'attachment id', :text => '1' do
294 294 assert_select '~ filename', :text => 'error281.txt'
295 295 assert_select '~ content_url', :text => 'http://www.example.com/attachments/download/1/error281.txt'
296 296 end
297 297 end
298 298 end
299 299
300 300 test "GET /issues/:id.xml with subtasks" do
301 301 issue = Issue.generate_with_descendants!(:project_id => 1)
302 302 get "/issues/#{issue.id}.xml?include=children"
303 303
304 304 assert_select 'issue id', :text => issue.id.to_s do
305 305 assert_select '~ children[type=array] > issue', 2
306 306 assert_select '~ children[type=array] > issue > children', 1
307 307 end
308 308 end
309 309
310 310 test "GET /issues/:id.json with subtasks" do
311 311 issue = Issue.generate_with_descendants!(:project_id => 1)
312 312 get "/issues/#{issue.id}.json?include=children"
313 313
314 314 json = ActiveSupport::JSON.decode(response.body)
315 315 assert_equal 2, json['issue']['children'].size
316 316 assert_equal 1, json['issue']['children'].select {|child| child.key?('children')}.size
317 317 end
318 318
319 319 def test_show_should_include_issue_attributes
320 320 get '/issues/1.xml'
321 321 assert_select 'issue>is_private', :text => 'false'
322 322 end
323 323
324 324 test "GET /issues/:id.xml?include=watchers should include watchers" do
325 325 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
326 326
327 327 get '/issues/1.xml?include=watchers', {}, credentials('jsmith')
328 328
329 329 assert_response :ok
330 330 assert_equal 'application/xml', response.content_type
331 331 assert_select 'issue' do
332 332 assert_select 'watchers', Issue.find(1).watchers.count
333 333 assert_select 'watchers' do
334 334 assert_select 'user[id="3"]'
335 335 end
336 336 end
337 337 end
338 338
339 339 test "POST /issues.xml should create an issue with the attributes" do
340 340
341 341 payload = <<-XML
342 342 <?xml version="1.0" encoding="UTF-8" ?>
343 343 <issue>
344 344 <project_id>1</project_id>
345 345 <tracker_id>2</tracker_id>
346 346 <status_id>3</status_id>
347 347 <subject>API test</subject>
348 348 </issue>
349 349 XML
350 350
351 351 assert_difference('Issue.count') do
352 352 post '/issues.xml', payload, {"CONTENT_TYPE" => 'application/xml'}.merge(credentials('jsmith'))
353 353 end
354 354 issue = Issue.order('id DESC').first
355 355 assert_equal 1, issue.project_id
356 356 assert_equal 2, issue.tracker_id
357 357 assert_equal 3, issue.status_id
358 358 assert_equal 'API test', issue.subject
359 359
360 360 assert_response :created
361 361 assert_equal 'application/xml', @response.content_type
362 362 assert_select 'issue > id', :text => issue.id.to_s
363 363 end
364 364
365 365 test "POST /issues.xml with watcher_user_ids should create issue with watchers" do
366 366 assert_difference('Issue.count') do
367 367 post '/issues.xml',
368 368 {:issue => {:project_id => 1, :subject => 'Watchers',
369 369 :tracker_id => 2, :status_id => 3, :watcher_user_ids => [3, 1]}}, credentials('jsmith')
370 370 assert_response :created
371 371 end
372 372 issue = Issue.order('id desc').first
373 373 assert_equal 2, issue.watchers.size
374 374 assert_equal [1, 3], issue.watcher_user_ids.sort
375 375 end
376 376
377 377 test "POST /issues.xml with failure should return errors" do
378 378 assert_no_difference('Issue.count') do
379 379 post '/issues.xml', {:issue => {:project_id => 1}}, credentials('jsmith')
380 380 end
381 381
382 382 assert_select 'errors error', :text => "Subject cannot be blank"
383 383 end
384 384
385 385 test "POST /issues.json should create an issue with the attributes" do
386 386
387 387 payload = <<-JSON
388 388 {
389 389 "issue": {
390 390 "project_id": "1",
391 391 "tracker_id": "2",
392 392 "status_id": "3",
393 393 "subject": "API test"
394 394 }
395 395 }
396 396 JSON
397 397
398 398 assert_difference('Issue.count') do
399 399 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
400 400 end
401 401
402 402 issue = Issue.order('id DESC').first
403 403 assert_equal 1, issue.project_id
404 404 assert_equal 2, issue.tracker_id
405 405 assert_equal 3, issue.status_id
406 406 assert_equal 'API test', issue.subject
407 407 end
408 408
409 409 test "POST /issues.json without tracker_id should accept custom fields" do
410 410 field = IssueCustomField.generate!(
411 411 :field_format => 'list',
412 412 :multiple => true,
413 413 :possible_values => ["V1", "V2", "V3"],
414 414 :default_value => "V2",
415 415 :is_for_all => true,
416 416 :trackers => Tracker.all.to_a
417 417 )
418 418
419 419 payload = <<-JSON
420 420 {
421 421 "issue": {
422 422 "project_id": "1",
423 423 "subject": "Multivalued custom field",
424 424 "custom_field_values":{"#{field.id}":["V1","V3"]}
425 425 }
426 426 }
427 427 JSON
428 428
429 429 assert_difference('Issue.count') do
430 430 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
431 431 end
432 432
433 433 assert_response :created
434 434 issue = Issue.order('id DESC').first
435 435 assert_equal ["V1", "V3"], issue.custom_field_value(field).sort
436 436 end
437 437
438 438 test "POST /issues.json with omitted custom field should set default value" do
439 439 field = IssueCustomField.generate!(:default_value => "Default")
440 440
441 441 issue = new_record(Issue) do
442 442 post '/issues.json',
443 443 {:issue => {:project_id => 1, :subject => 'API', :custom_field_values => {}}},
444 444 credentials('jsmith')
445 445 end
446 446 assert_equal "Default", issue.custom_field_value(field)
447 447 end
448 448
449 449 test "POST /issues.json with custom field set to blank should not set default value" do
450 450 field = IssueCustomField.generate!(:default_value => "Default")
451 451
452 452 issue = new_record(Issue) do
453 453 post '/issues.json',
454 454 {:issue => {:project_id => 1, :subject => 'API', :custom_field_values => {field.id.to_s => ""}}},
455 455 credentials('jsmith')
456 456 end
457 457 assert_equal "", issue.custom_field_value(field)
458 458 end
459 459
460 460 test "POST /issues.json with failure should return errors" do
461 461 assert_no_difference('Issue.count') do
462 462 post '/issues.json', {:issue => {:project_id => 1}}, credentials('jsmith')
463 463 end
464 464
465 465 json = ActiveSupport::JSON.decode(response.body)
466 466 assert json['errors'].include?("Subject cannot be blank")
467 467 end
468 468
469 469 test "POST /issues.json with invalid project_id should respond with 422" do
470 470 post '/issues.json', {:issue => {:project_id => 999, :subject => "API"}}, credentials('jsmith')
471 471 assert_response 422
472 472 end
473 473
474 474 test "PUT /issues/:id.xml" do
475 475 assert_difference('Journal.count') do
476 476 put '/issues/6.xml',
477 477 {:issue => {:subject => 'API update', :notes => 'A new note'}},
478 478 credentials('jsmith')
479 479 end
480 480
481 481 issue = Issue.find(6)
482 482 assert_equal "API update", issue.subject
483 483 journal = Journal.last
484 484 assert_equal "A new note", journal.notes
485 485 end
486 486
487 487 test "PUT /issues/:id.xml with custom fields" do
488 488 put '/issues/3.xml',
489 489 {:issue => {:custom_fields => [
490 490 {'id' => '1', 'value' => 'PostgreSQL' },
491 491 {'id' => '2', 'value' => '150'}
492 492 ]}},
493 493 credentials('jsmith')
494 494
495 495 issue = Issue.find(3)
496 496 assert_equal '150', issue.custom_value_for(2).value
497 497 assert_equal 'PostgreSQL', issue.custom_value_for(1).value
498 498 end
499 499
500 500 test "PUT /issues/:id.xml with multi custom fields" do
501 501 field = CustomField.find(1)
502 502 field.update_attribute :multiple, true
503 503
504 504 put '/issues/3.xml',
505 505 {:issue => {:custom_fields => [
506 506 {'id' => '1', 'value' => ['MySQL', 'PostgreSQL'] },
507 507 {'id' => '2', 'value' => '150'}
508 508 ]}},
509 509 credentials('jsmith')
510 510
511 511 issue = Issue.find(3)
512 512 assert_equal '150', issue.custom_value_for(2).value
513 513 assert_equal ['MySQL', 'PostgreSQL'], issue.custom_field_value(1).sort
514 514 end
515 515
516 516 test "PUT /issues/:id.xml with project change" do
517 517 put '/issues/3.xml',
518 518 {:issue => {:project_id => 2, :subject => 'Project changed'}},
519 519 credentials('jsmith')
520 520
521 521 issue = Issue.find(3)
522 522 assert_equal 2, issue.project_id
523 523 assert_equal 'Project changed', issue.subject
524 524 end
525 525
526 526 test "PUT /issues/:id.xml with notes only" do
527 527 assert_difference('Journal.count') do
528 528 put '/issues/6.xml',
529 529 {:issue => {:notes => 'Notes only'}},
530 530 credentials('jsmith')
531 531 end
532 532
533 533 journal = Journal.last
534 534 assert_equal "Notes only", journal.notes
535 535 end
536 536
537 537 test "PUT /issues/:id.json with omitted custom field should not change blank value to default value" do
538 538 field = IssueCustomField.generate!(:default_value => "Default")
539 539 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => ""})
540 540 assert_equal "", issue.reload.custom_field_value(field)
541 541
542 542 assert_difference('Journal.count') do
543 543 put "/issues/#{issue.id}.json",
544 544 {:issue => {:custom_field_values => {}, :notes => 'API'}},
545 545 credentials('jsmith')
546 546 end
547 547
548 548 assert_equal "", issue.reload.custom_field_value(field)
549 549 end
550 550
551 551 test "PUT /issues/:id.json with custom field set to blank should not change blank value to default value" do
552 552 field = IssueCustomField.generate!(:default_value => "Default")
553 553 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => ""})
554 554 assert_equal "", issue.reload.custom_field_value(field)
555 555
556 556 assert_difference('Journal.count') do
557 557 put "/issues/#{issue.id}.json",
558 558 {:issue => {:custom_field_values => {field.id.to_s => ""}, :notes => 'API'}},
559 559 credentials('jsmith')
560 560 end
561 561
562 562 assert_equal "", issue.reload.custom_field_value(field)
563 563 end
564 564
565 test "PUT /issues/:id.json with tracker change and omitted custom field specific to that tracker does not set default value" do
565 test "PUT /issues/:id.json with tracker change and omitted custom field specific to that tracker should set default value" do
566 566 field = IssueCustomField.generate!(:default_value => "Default", :tracker_ids => [2])
567 567 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
568 568
569 569 assert_difference('Journal.count') do
570 570 put "/issues/#{issue.id}.json",
571 571 {:issue => {:tracker_id => 2, :custom_field_values => {}, :notes => 'API'}},
572 572 credentials('jsmith')
573 573 end
574 574
575 575 assert_equal 2, issue.reload.tracker_id
576 assert_nil issue.reload.custom_field_value(field)
576 assert_equal "Default", issue.reload.custom_field_value(field)
577 577 end
578 578
579 579 test "PUT /issues/:id.json with tracker change and custom field specific to that tracker set to blank should not set default value" do
580 580 field = IssueCustomField.generate!(:default_value => "Default", :tracker_ids => [2])
581 581 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
582 582
583 583 assert_difference('Journal.count') do
584 584 put "/issues/#{issue.id}.json",
585 585 {:issue => {:tracker_id => 2, :custom_field_values => {field.id.to_s => ""}, :notes => 'API'}},
586 586 credentials('jsmith')
587 587 end
588 588
589 589 assert_equal 2, issue.reload.tracker_id
590 590 assert_equal "", issue.reload.custom_field_value(field)
591 591 end
592 592
593 593 test "PUT /issues/:id.xml with failed update" do
594 594 put '/issues/6.xml', {:issue => {:subject => ''}}, credentials('jsmith')
595 595
596 596 assert_response :unprocessable_entity
597 597 assert_select 'errors error', :text => "Subject cannot be blank"
598 598 end
599 599
600 600 test "PUT /issues/:id.json" do
601 601 assert_difference('Journal.count') do
602 602 put '/issues/6.json',
603 603 {:issue => {:subject => 'API update', :notes => 'A new note'}},
604 604 credentials('jsmith')
605 605
606 606 assert_response :ok
607 607 assert_equal '', response.body
608 608 end
609 609
610 610 issue = Issue.find(6)
611 611 assert_equal "API update", issue.subject
612 612 journal = Journal.last
613 613 assert_equal "A new note", journal.notes
614 614 end
615 615
616 616 test "PUT /issues/:id.json with failed update" do
617 617 put '/issues/6.json', {:issue => {:subject => ''}}, credentials('jsmith')
618 618
619 619 assert_response :unprocessable_entity
620 620 json = ActiveSupport::JSON.decode(response.body)
621 621 assert json['errors'].include?("Subject cannot be blank")
622 622 end
623 623
624 624 test "DELETE /issues/:id.xml" do
625 625 assert_difference('Issue.count', -1) do
626 626 delete '/issues/6.xml', {}, credentials('jsmith')
627 627
628 628 assert_response :ok
629 629 assert_equal '', response.body
630 630 end
631 631 assert_nil Issue.find_by_id(6)
632 632 end
633 633
634 634 test "DELETE /issues/:id.json" do
635 635 assert_difference('Issue.count', -1) do
636 636 delete '/issues/6.json', {}, credentials('jsmith')
637 637
638 638 assert_response :ok
639 639 assert_equal '', response.body
640 640 end
641 641 assert_nil Issue.find_by_id(6)
642 642 end
643 643
644 644 test "POST /issues/:id/watchers.xml should add watcher" do
645 645 assert_difference 'Watcher.count' do
646 646 post '/issues/1/watchers.xml', {:user_id => 3}, credentials('jsmith')
647 647
648 648 assert_response :ok
649 649 assert_equal '', response.body
650 650 end
651 651 watcher = Watcher.order('id desc').first
652 652 assert_equal Issue.find(1), watcher.watchable
653 653 assert_equal User.find(3), watcher.user
654 654 end
655 655
656 656 test "DELETE /issues/:id/watchers/:user_id.xml should remove watcher" do
657 657 Watcher.create!(:user_id => 3, :watchable => Issue.find(1))
658 658
659 659 assert_difference 'Watcher.count', -1 do
660 660 delete '/issues/1/watchers/3.xml', {}, credentials('jsmith')
661 661
662 662 assert_response :ok
663 663 assert_equal '', response.body
664 664 end
665 665 assert_equal false, Issue.find(1).watched_by?(User.find(3))
666 666 end
667 667
668 668 def test_create_issue_with_uploaded_file
669 669 token = xml_upload('test_create_with_upload', credentials('jsmith'))
670 670 attachment = Attachment.find_by_token(token)
671 671
672 672 # create the issue with the upload's token
673 673 assert_difference 'Issue.count' do
674 674 post '/issues.xml',
675 675 {:issue => {:project_id => 1, :subject => 'Uploaded file',
676 676 :uploads => [{:token => token, :filename => 'test.txt',
677 677 :content_type => 'text/plain'}]}},
678 678 credentials('jsmith')
679 679 assert_response :created
680 680 end
681 681 issue = Issue.order('id DESC').first
682 682 assert_equal 1, issue.attachments.count
683 683 assert_equal attachment, issue.attachments.first
684 684
685 685 attachment.reload
686 686 assert_equal 'test.txt', attachment.filename
687 687 assert_equal 'text/plain', attachment.content_type
688 688 assert_equal 'test_create_with_upload'.size, attachment.filesize
689 689 assert_equal 2, attachment.author_id
690 690
691 691 # get the issue with its attachments
692 692 get "/issues/#{issue.id}.xml", :include => 'attachments'
693 693 assert_response :success
694 694 xml = Hash.from_xml(response.body)
695 695 attachments = xml['issue']['attachments']
696 696 assert_kind_of Array, attachments
697 697 assert_equal 1, attachments.size
698 698 url = attachments.first['content_url']
699 699 assert_not_nil url
700 700
701 701 # download the attachment
702 702 get url
703 703 assert_response :success
704 704 assert_equal 'test_create_with_upload', response.body
705 705 end
706 706
707 707 def test_create_issue_with_multiple_uploaded_files_as_xml
708 708 token1 = xml_upload('File content 1', credentials('jsmith'))
709 709 token2 = xml_upload('File content 2', credentials('jsmith'))
710 710
711 711 payload = <<-XML
712 712 <?xml version="1.0" encoding="UTF-8" ?>
713 713 <issue>
714 714 <project_id>1</project_id>
715 715 <tracker_id>1</tracker_id>
716 716 <subject>Issue with multiple attachments</subject>
717 717 <uploads type="array">
718 718 <upload>
719 719 <token>#{token1}</token>
720 720 <filename>test1.txt</filename>
721 721 </upload>
722 722 <upload>
723 723 <token>#{token2}</token>
724 724 <filename>test1.txt</filename>
725 725 </upload>
726 726 </uploads>
727 727 </issue>
728 728 XML
729 729
730 730 assert_difference 'Issue.count' do
731 731 post '/issues.xml', payload, {"CONTENT_TYPE" => 'application/xml'}.merge(credentials('jsmith'))
732 732 assert_response :created
733 733 end
734 734 issue = Issue.order('id DESC').first
735 735 assert_equal 2, issue.attachments.count
736 736 end
737 737
738 738 def test_create_issue_with_multiple_uploaded_files_as_json
739 739 token1 = json_upload('File content 1', credentials('jsmith'))
740 740 token2 = json_upload('File content 2', credentials('jsmith'))
741 741
742 742 payload = <<-JSON
743 743 {
744 744 "issue": {
745 745 "project_id": "1",
746 746 "tracker_id": "1",
747 747 "subject": "Issue with multiple attachments",
748 748 "uploads": [
749 749 {"token": "#{token1}", "filename": "test1.txt"},
750 750 {"token": "#{token2}", "filename": "test2.txt"}
751 751 ]
752 752 }
753 753 }
754 754 JSON
755 755
756 756 assert_difference 'Issue.count' do
757 757 post '/issues.json', payload, {"CONTENT_TYPE" => 'application/json'}.merge(credentials('jsmith'))
758 758 assert_response :created
759 759 end
760 760 issue = Issue.order('id DESC').first
761 761 assert_equal 2, issue.attachments.count
762 762 end
763 763
764 764 def test_update_issue_with_uploaded_file
765 765 token = xml_upload('test_upload_with_upload', credentials('jsmith'))
766 766 attachment = Attachment.find_by_token(token)
767 767
768 768 # update the issue with the upload's token
769 769 assert_difference 'Journal.count' do
770 770 put '/issues/1.xml',
771 771 {:issue => {:notes => 'Attachment added',
772 772 :uploads => [{:token => token, :filename => 'test.txt',
773 773 :content_type => 'text/plain'}]}},
774 774 credentials('jsmith')
775 775 assert_response :ok
776 776 assert_equal '', @response.body
777 777 end
778 778
779 779 issue = Issue.find(1)
780 780 assert_include attachment, issue.attachments
781 781 end
782 782 end
@@ -1,39 +1,49
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class CustomValueTest < ActiveSupport::TestCase
21 21 fixtures :custom_fields, :custom_values, :users
22 22
23 def test_default_value
24 field = CustomField.find_by_default_value('Default string')
25 assert_not_nil field
23 def test_new_without_value_should_set_default_value
24 field = CustomField.generate!(:default_value => 'Default string')
26 25
27 26 v = CustomValue.new(:custom_field => field)
28 27 assert_equal 'Default string', v.value
28 end
29
30 def test_new_with_value_should_not_set_default_value
31 field = CustomField.generate!(:default_value => 'Default string')
32
33 v = CustomValue.new(:custom_field => field, :value => 'String')
34 assert_equal 'String', v.value
35 end
36
37 def test_new_with_nil_value_should_not_set_default_value
38 field = CustomField.generate!(:default_value => 'Default string')
29 39
30 v = CustomValue.new(:custom_field => field, :value => 'Not empty')
31 assert_equal 'Not empty', v.value
40 v = CustomValue.new(:custom_field => field, :value => nil)
41 assert_nil v.value
32 42 end
33 43
34 44 def test_sti_polymorphic_association
35 45 # Rails uses top level sti class for polymorphic association. See #3978.
36 46 assert !User.find(4).custom_values.empty?
37 47 assert !CustomValue.find(2).customized.nil?
38 48 end
39 49 end
General Comments 0
You need to be logged in to leave comments. Login now