##// END OF EJS Templates
Fixed that custom values get saved when assigning custom values after changing to a tracker with different custom fields (#9737)....
Jean-Philippe Lang -
r7983:a63027e1755f
parent child
Show More
@@ -1,1134 +1,1150
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles,
21 fixtures :projects, :users, :members, :member_roles, :roles,
22 :trackers, :projects_trackers,
22 :trackers, :projects_trackers,
23 :enabled_modules,
23 :enabled_modules,
24 :versions,
24 :versions,
25 :issue_statuses, :issue_categories, :issue_relations, :workflows,
25 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 :enumerations,
26 :enumerations,
27 :issues,
27 :issues,
28 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
28 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :time_entries
29 :time_entries
30
30
31 def test_create
31 def test_create
32 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
32 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
33 :status_id => 1, :priority => IssuePriority.all.first,
33 :status_id => 1, :priority => IssuePriority.all.first,
34 :subject => 'test_create',
34 :subject => 'test_create',
35 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
35 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
36 assert issue.save
36 assert issue.save
37 issue.reload
37 issue.reload
38 assert_equal 1.5, issue.estimated_hours
38 assert_equal 1.5, issue.estimated_hours
39 end
39 end
40
40
41 def test_create_minimal
41 def test_create_minimal
42 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
42 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
43 :status_id => 1, :priority => IssuePriority.all.first,
43 :status_id => 1, :priority => IssuePriority.all.first,
44 :subject => 'test_create')
44 :subject => 'test_create')
45 assert issue.save
45 assert issue.save
46 assert issue.description.nil?
46 assert issue.description.nil?
47 end
47 end
48
48
49 def test_create_with_required_custom_field
49 def test_create_with_required_custom_field
50 field = IssueCustomField.find_by_name('Database')
50 field = IssueCustomField.find_by_name('Database')
51 field.update_attribute(:is_required, true)
51 field.update_attribute(:is_required, true)
52
52
53 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
53 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
54 :status_id => 1, :subject => 'test_create',
54 :status_id => 1, :subject => 'test_create',
55 :description => 'IssueTest#test_create_with_required_custom_field')
55 :description => 'IssueTest#test_create_with_required_custom_field')
56 assert issue.available_custom_fields.include?(field)
56 assert issue.available_custom_fields.include?(field)
57 # No value for the custom field
57 # No value for the custom field
58 assert !issue.save
58 assert !issue.save
59 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
59 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
60 # Blank value
60 # Blank value
61 issue.custom_field_values = { field.id => '' }
61 issue.custom_field_values = { field.id => '' }
62 assert !issue.save
62 assert !issue.save
63 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
63 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
64 # Invalid value
64 # Invalid value
65 issue.custom_field_values = { field.id => 'SQLServer' }
65 issue.custom_field_values = { field.id => 'SQLServer' }
66 assert !issue.save
66 assert !issue.save
67 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
67 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
68 # Valid value
68 # Valid value
69 issue.custom_field_values = { field.id => 'PostgreSQL' }
69 issue.custom_field_values = { field.id => 'PostgreSQL' }
70 assert issue.save
70 assert issue.save
71 issue.reload
71 issue.reload
72 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
72 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
73 end
73 end
74
74
75 def test_create_with_group_assignment
75 def test_create_with_group_assignment
76 with_settings :issue_group_assignment => '1' do
76 with_settings :issue_group_assignment => '1' do
77 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
77 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
78 :subject => 'Group assignment',
78 :subject => 'Group assignment',
79 :assigned_to_id => 11).save
79 :assigned_to_id => 11).save
80 issue = Issue.first(:order => 'id DESC')
80 issue = Issue.first(:order => 'id DESC')
81 assert_kind_of Group, issue.assigned_to
81 assert_kind_of Group, issue.assigned_to
82 assert_equal Group.find(11), issue.assigned_to
82 assert_equal Group.find(11), issue.assigned_to
83 end
83 end
84 end
84 end
85
85
86 def assert_visibility_match(user, issues)
86 def assert_visibility_match(user, issues)
87 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
87 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
88 end
88 end
89
89
90 def test_visible_scope_for_anonymous
90 def test_visible_scope_for_anonymous
91 # Anonymous user should see issues of public projects only
91 # Anonymous user should see issues of public projects only
92 issues = Issue.visible(User.anonymous).all
92 issues = Issue.visible(User.anonymous).all
93 assert issues.any?
93 assert issues.any?
94 assert_nil issues.detect {|issue| !issue.project.is_public?}
94 assert_nil issues.detect {|issue| !issue.project.is_public?}
95 assert_nil issues.detect {|issue| issue.is_private?}
95 assert_nil issues.detect {|issue| issue.is_private?}
96 assert_visibility_match User.anonymous, issues
96 assert_visibility_match User.anonymous, issues
97 end
97 end
98
98
99 def test_visible_scope_for_anonymous_with_own_issues_visibility
99 def test_visible_scope_for_anonymous_with_own_issues_visibility
100 Role.anonymous.update_attribute :issues_visibility, 'own'
100 Role.anonymous.update_attribute :issues_visibility, 'own'
101 Issue.create!(:project_id => 1, :tracker_id => 1,
101 Issue.create!(:project_id => 1, :tracker_id => 1,
102 :author_id => User.anonymous.id,
102 :author_id => User.anonymous.id,
103 :subject => 'Issue by anonymous')
103 :subject => 'Issue by anonymous')
104
104
105 issues = Issue.visible(User.anonymous).all
105 issues = Issue.visible(User.anonymous).all
106 assert issues.any?
106 assert issues.any?
107 assert_nil issues.detect {|issue| issue.author != User.anonymous}
107 assert_nil issues.detect {|issue| issue.author != User.anonymous}
108 assert_visibility_match User.anonymous, issues
108 assert_visibility_match User.anonymous, issues
109 end
109 end
110
110
111 def test_visible_scope_for_anonymous_without_view_issues_permissions
111 def test_visible_scope_for_anonymous_without_view_issues_permissions
112 # Anonymous user should not see issues without permission
112 # Anonymous user should not see issues without permission
113 Role.anonymous.remove_permission!(:view_issues)
113 Role.anonymous.remove_permission!(:view_issues)
114 issues = Issue.visible(User.anonymous).all
114 issues = Issue.visible(User.anonymous).all
115 assert issues.empty?
115 assert issues.empty?
116 assert_visibility_match User.anonymous, issues
116 assert_visibility_match User.anonymous, issues
117 end
117 end
118
118
119 def test_visible_scope_for_non_member
119 def test_visible_scope_for_non_member
120 user = User.find(9)
120 user = User.find(9)
121 assert user.projects.empty?
121 assert user.projects.empty?
122 # Non member user should see issues of public projects only
122 # Non member user should see issues of public projects only
123 issues = Issue.visible(user).all
123 issues = Issue.visible(user).all
124 assert issues.any?
124 assert issues.any?
125 assert_nil issues.detect {|issue| !issue.project.is_public?}
125 assert_nil issues.detect {|issue| !issue.project.is_public?}
126 assert_nil issues.detect {|issue| issue.is_private?}
126 assert_nil issues.detect {|issue| issue.is_private?}
127 assert_visibility_match user, issues
127 assert_visibility_match user, issues
128 end
128 end
129
129
130 def test_visible_scope_for_non_member_with_own_issues_visibility
130 def test_visible_scope_for_non_member_with_own_issues_visibility
131 Role.non_member.update_attribute :issues_visibility, 'own'
131 Role.non_member.update_attribute :issues_visibility, 'own'
132 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
132 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
133 user = User.find(9)
133 user = User.find(9)
134
134
135 issues = Issue.visible(user).all
135 issues = Issue.visible(user).all
136 assert issues.any?
136 assert issues.any?
137 assert_nil issues.detect {|issue| issue.author != user}
137 assert_nil issues.detect {|issue| issue.author != user}
138 assert_visibility_match user, issues
138 assert_visibility_match user, issues
139 end
139 end
140
140
141 def test_visible_scope_for_non_member_without_view_issues_permissions
141 def test_visible_scope_for_non_member_without_view_issues_permissions
142 # Non member user should not see issues without permission
142 # Non member user should not see issues without permission
143 Role.non_member.remove_permission!(:view_issues)
143 Role.non_member.remove_permission!(:view_issues)
144 user = User.find(9)
144 user = User.find(9)
145 assert user.projects.empty?
145 assert user.projects.empty?
146 issues = Issue.visible(user).all
146 issues = Issue.visible(user).all
147 assert issues.empty?
147 assert issues.empty?
148 assert_visibility_match user, issues
148 assert_visibility_match user, issues
149 end
149 end
150
150
151 def test_visible_scope_for_member
151 def test_visible_scope_for_member
152 user = User.find(9)
152 user = User.find(9)
153 # User should see issues of projects for which he has view_issues permissions only
153 # User should see issues of projects for which he has view_issues permissions only
154 Role.non_member.remove_permission!(:view_issues)
154 Role.non_member.remove_permission!(:view_issues)
155 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
155 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
156 issues = Issue.visible(user).all
156 issues = Issue.visible(user).all
157 assert issues.any?
157 assert issues.any?
158 assert_nil issues.detect {|issue| issue.project_id != 3}
158 assert_nil issues.detect {|issue| issue.project_id != 3}
159 assert_nil issues.detect {|issue| issue.is_private?}
159 assert_nil issues.detect {|issue| issue.is_private?}
160 assert_visibility_match user, issues
160 assert_visibility_match user, issues
161 end
161 end
162
162
163 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
163 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
164 user = User.find(8)
164 user = User.find(8)
165 assert user.groups.any?
165 assert user.groups.any?
166 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
166 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
167 Role.non_member.remove_permission!(:view_issues)
167 Role.non_member.remove_permission!(:view_issues)
168
168
169 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
169 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
170 :status_id => 1, :priority => IssuePriority.all.first,
170 :status_id => 1, :priority => IssuePriority.all.first,
171 :subject => 'Assignment test',
171 :subject => 'Assignment test',
172 :assigned_to => user.groups.first,
172 :assigned_to => user.groups.first,
173 :is_private => true)
173 :is_private => true)
174
174
175 Role.find(2).update_attribute :issues_visibility, 'default'
175 Role.find(2).update_attribute :issues_visibility, 'default'
176 issues = Issue.visible(User.find(8)).all
176 issues = Issue.visible(User.find(8)).all
177 assert issues.any?
177 assert issues.any?
178 assert issues.include?(issue)
178 assert issues.include?(issue)
179
179
180 Role.find(2).update_attribute :issues_visibility, 'own'
180 Role.find(2).update_attribute :issues_visibility, 'own'
181 issues = Issue.visible(User.find(8)).all
181 issues = Issue.visible(User.find(8)).all
182 assert issues.any?
182 assert issues.any?
183 assert issues.include?(issue)
183 assert issues.include?(issue)
184 end
184 end
185
185
186 def test_visible_scope_for_admin
186 def test_visible_scope_for_admin
187 user = User.find(1)
187 user = User.find(1)
188 user.members.each(&:destroy)
188 user.members.each(&:destroy)
189 assert user.projects.empty?
189 assert user.projects.empty?
190 issues = Issue.visible(user).all
190 issues = Issue.visible(user).all
191 assert issues.any?
191 assert issues.any?
192 # Admin should see issues on private projects that he does not belong to
192 # Admin should see issues on private projects that he does not belong to
193 assert issues.detect {|issue| !issue.project.is_public?}
193 assert issues.detect {|issue| !issue.project.is_public?}
194 # Admin should see private issues of other users
194 # Admin should see private issues of other users
195 assert issues.detect {|issue| issue.is_private? && issue.author != user}
195 assert issues.detect {|issue| issue.is_private? && issue.author != user}
196 assert_visibility_match user, issues
196 assert_visibility_match user, issues
197 end
197 end
198
198
199 def test_visible_scope_with_project
199 def test_visible_scope_with_project
200 project = Project.find(1)
200 project = Project.find(1)
201 issues = Issue.visible(User.find(2), :project => project).all
201 issues = Issue.visible(User.find(2), :project => project).all
202 projects = issues.collect(&:project).uniq
202 projects = issues.collect(&:project).uniq
203 assert_equal 1, projects.size
203 assert_equal 1, projects.size
204 assert_equal project, projects.first
204 assert_equal project, projects.first
205 end
205 end
206
206
207 def test_visible_scope_with_project_and_subprojects
207 def test_visible_scope_with_project_and_subprojects
208 project = Project.find(1)
208 project = Project.find(1)
209 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
209 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
210 projects = issues.collect(&:project).uniq
210 projects = issues.collect(&:project).uniq
211 assert projects.size > 1
211 assert projects.size > 1
212 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
212 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
213 end
213 end
214
214
215 def test_visible_and_nested_set_scopes
215 def test_visible_and_nested_set_scopes
216 assert_equal 0, Issue.find(1).descendants.visible.all.size
216 assert_equal 0, Issue.find(1).descendants.visible.all.size
217 end
217 end
218
218
219 def test_errors_full_messages_should_include_custom_fields_errors
219 def test_errors_full_messages_should_include_custom_fields_errors
220 field = IssueCustomField.find_by_name('Database')
220 field = IssueCustomField.find_by_name('Database')
221
221
222 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
222 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
223 :status_id => 1, :subject => 'test_create',
223 :status_id => 1, :subject => 'test_create',
224 :description => 'IssueTest#test_create_with_required_custom_field')
224 :description => 'IssueTest#test_create_with_required_custom_field')
225 assert issue.available_custom_fields.include?(field)
225 assert issue.available_custom_fields.include?(field)
226 # Invalid value
226 # Invalid value
227 issue.custom_field_values = { field.id => 'SQLServer' }
227 issue.custom_field_values = { field.id => 'SQLServer' }
228
228
229 assert !issue.valid?
229 assert !issue.valid?
230 assert_equal 1, issue.errors.full_messages.size
230 assert_equal 1, issue.errors.full_messages.size
231 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
231 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
232 issue.errors.full_messages.first
232 issue.errors.full_messages.first
233 end
233 end
234
234
235 def test_update_issue_with_required_custom_field
235 def test_update_issue_with_required_custom_field
236 field = IssueCustomField.find_by_name('Database')
236 field = IssueCustomField.find_by_name('Database')
237 field.update_attribute(:is_required, true)
237 field.update_attribute(:is_required, true)
238
238
239 issue = Issue.find(1)
239 issue = Issue.find(1)
240 assert_nil issue.custom_value_for(field)
240 assert_nil issue.custom_value_for(field)
241 assert issue.available_custom_fields.include?(field)
241 assert issue.available_custom_fields.include?(field)
242 # No change to custom values, issue can be saved
242 # No change to custom values, issue can be saved
243 assert issue.save
243 assert issue.save
244 # Blank value
244 # Blank value
245 issue.custom_field_values = { field.id => '' }
245 issue.custom_field_values = { field.id => '' }
246 assert !issue.save
246 assert !issue.save
247 # Valid value
247 # Valid value
248 issue.custom_field_values = { field.id => 'PostgreSQL' }
248 issue.custom_field_values = { field.id => 'PostgreSQL' }
249 assert issue.save
249 assert issue.save
250 issue.reload
250 issue.reload
251 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
251 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
252 end
252 end
253
253
254 def test_should_not_update_attributes_if_custom_fields_validation_fails
254 def test_should_not_update_attributes_if_custom_fields_validation_fails
255 issue = Issue.find(1)
255 issue = Issue.find(1)
256 field = IssueCustomField.find_by_name('Database')
256 field = IssueCustomField.find_by_name('Database')
257 assert issue.available_custom_fields.include?(field)
257 assert issue.available_custom_fields.include?(field)
258
258
259 issue.custom_field_values = { field.id => 'Invalid' }
259 issue.custom_field_values = { field.id => 'Invalid' }
260 issue.subject = 'Should be not be saved'
260 issue.subject = 'Should be not be saved'
261 assert !issue.save
261 assert !issue.save
262
262
263 issue.reload
263 issue.reload
264 assert_equal "Can't print recipes", issue.subject
264 assert_equal "Can't print recipes", issue.subject
265 end
265 end
266
266
267 def test_should_not_recreate_custom_values_objects_on_update
267 def test_should_not_recreate_custom_values_objects_on_update
268 field = IssueCustomField.find_by_name('Database')
268 field = IssueCustomField.find_by_name('Database')
269
269
270 issue = Issue.find(1)
270 issue = Issue.find(1)
271 issue.custom_field_values = { field.id => 'PostgreSQL' }
271 issue.custom_field_values = { field.id => 'PostgreSQL' }
272 assert issue.save
272 assert issue.save
273 custom_value = issue.custom_value_for(field)
273 custom_value = issue.custom_value_for(field)
274 issue.reload
274 issue.reload
275 issue.custom_field_values = { field.id => 'MySQL' }
275 issue.custom_field_values = { field.id => 'MySQL' }
276 assert issue.save
276 assert issue.save
277 issue.reload
277 issue.reload
278 assert_equal custom_value.id, issue.custom_value_for(field).id
278 assert_equal custom_value.id, issue.custom_value_for(field).id
279 end
279 end
280
280
281 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
282 issue = Issue.new(:project_id => 1)
283 issue.attributes = {:tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'Test', :custom_field_values => {'2' => 'Test'}}
284 issue.save!
285
286 assert !Tracker.find(2).custom_field_ids.include?(2)
287
288 issue = Issue.find(issue.id)
289 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
290
291 issue = Issue.find(issue.id)
292 custom_value = issue.custom_value_for(2)
293 assert_not_nil custom_value
294 assert_equal 'Test', custom_value.value
295 end
296
281 def test_assigning_tracker_id_should_reload_custom_fields_values
297 def test_assigning_tracker_id_should_reload_custom_fields_values
282 issue = Issue.new(:project => Project.find(1))
298 issue = Issue.new(:project => Project.find(1))
283 assert issue.custom_field_values.empty?
299 assert issue.custom_field_values.empty?
284 issue.tracker_id = 1
300 issue.tracker_id = 1
285 assert issue.custom_field_values.any?
301 assert issue.custom_field_values.any?
286 end
302 end
287
303
288 def test_assigning_attributes_should_assign_tracker_id_first
304 def test_assigning_attributes_should_assign_tracker_id_first
289 attributes = ActiveSupport::OrderedHash.new
305 attributes = ActiveSupport::OrderedHash.new
290 attributes['custom_field_values'] = { '1' => 'MySQL' }
306 attributes['custom_field_values'] = { '1' => 'MySQL' }
291 attributes['tracker_id'] = '1'
307 attributes['tracker_id'] = '1'
292 issue = Issue.new(:project => Project.find(1))
308 issue = Issue.new(:project => Project.find(1))
293 issue.attributes = attributes
309 issue.attributes = attributes
294 assert_not_nil issue.custom_value_for(1)
310 assert_not_nil issue.custom_value_for(1)
295 assert_equal 'MySQL', issue.custom_value_for(1).value
311 assert_equal 'MySQL', issue.custom_value_for(1).value
296 end
312 end
297
313
298 def test_should_update_issue_with_disabled_tracker
314 def test_should_update_issue_with_disabled_tracker
299 p = Project.find(1)
315 p = Project.find(1)
300 issue = Issue.find(1)
316 issue = Issue.find(1)
301
317
302 p.trackers.delete(issue.tracker)
318 p.trackers.delete(issue.tracker)
303 assert !p.trackers.include?(issue.tracker)
319 assert !p.trackers.include?(issue.tracker)
304
320
305 issue.reload
321 issue.reload
306 issue.subject = 'New subject'
322 issue.subject = 'New subject'
307 assert issue.save
323 assert issue.save
308 end
324 end
309
325
310 def test_should_not_set_a_disabled_tracker
326 def test_should_not_set_a_disabled_tracker
311 p = Project.find(1)
327 p = Project.find(1)
312 p.trackers.delete(Tracker.find(2))
328 p.trackers.delete(Tracker.find(2))
313
329
314 issue = Issue.find(1)
330 issue = Issue.find(1)
315 issue.tracker_id = 2
331 issue.tracker_id = 2
316 issue.subject = 'New subject'
332 issue.subject = 'New subject'
317 assert !issue.save
333 assert !issue.save
318 assert_not_nil issue.errors[:tracker_id]
334 assert_not_nil issue.errors[:tracker_id]
319 end
335 end
320
336
321 def test_category_based_assignment
337 def test_category_based_assignment
322 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
338 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
323 :status_id => 1, :priority => IssuePriority.all.first,
339 :status_id => 1, :priority => IssuePriority.all.first,
324 :subject => 'Assignment test',
340 :subject => 'Assignment test',
325 :description => 'Assignment test', :category_id => 1)
341 :description => 'Assignment test', :category_id => 1)
326 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
342 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
327 end
343 end
328
344
329 def test_new_statuses_allowed_to
345 def test_new_statuses_allowed_to
330 Workflow.delete_all
346 Workflow.delete_all
331
347
332 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
348 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2, :author => false, :assignee => false)
333 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
349 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3, :author => true, :assignee => false)
334 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
350 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4, :author => false, :assignee => true)
335 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
351 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5, :author => true, :assignee => true)
336 status = IssueStatus.find(1)
352 status = IssueStatus.find(1)
337 role = Role.find(1)
353 role = Role.find(1)
338 tracker = Tracker.find(1)
354 tracker = Tracker.find(1)
339 user = User.find(2)
355 user = User.find(2)
340
356
341 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
357 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1)
342 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
358 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
343
359
344 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
360 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user)
345 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
361 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
346
362
347 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
363 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :assigned_to => user)
348 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
364 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
349
365
350 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
366 issue = Issue.generate!(:tracker => tracker, :status => status, :project_id => 1, :author => user, :assigned_to => user)
351 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
367 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
352 end
368 end
353
369
354 def test_copy
370 def test_copy
355 issue = Issue.new.copy_from(1)
371 issue = Issue.new.copy_from(1)
356 assert issue.save
372 assert issue.save
357 issue.reload
373 issue.reload
358 orig = Issue.find(1)
374 orig = Issue.find(1)
359 assert_equal orig.subject, issue.subject
375 assert_equal orig.subject, issue.subject
360 assert_equal orig.tracker, issue.tracker
376 assert_equal orig.tracker, issue.tracker
361 assert_equal "125", issue.custom_value_for(2).value
377 assert_equal "125", issue.custom_value_for(2).value
362 end
378 end
363
379
364 def test_copy_should_copy_status
380 def test_copy_should_copy_status
365 orig = Issue.find(8)
381 orig = Issue.find(8)
366 assert orig.status != IssueStatus.default
382 assert orig.status != IssueStatus.default
367
383
368 issue = Issue.new.copy_from(orig)
384 issue = Issue.new.copy_from(orig)
369 assert issue.save
385 assert issue.save
370 issue.reload
386 issue.reload
371 assert_equal orig.status, issue.status
387 assert_equal orig.status, issue.status
372 end
388 end
373
389
374 def test_should_close_duplicates
390 def test_should_close_duplicates
375 # Create 3 issues
391 # Create 3 issues
376 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
392 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
377 :status_id => 1, :priority => IssuePriority.all.first,
393 :status_id => 1, :priority => IssuePriority.all.first,
378 :subject => 'Duplicates test', :description => 'Duplicates test')
394 :subject => 'Duplicates test', :description => 'Duplicates test')
379 assert issue1.save
395 assert issue1.save
380 issue2 = issue1.clone
396 issue2 = issue1.clone
381 assert issue2.save
397 assert issue2.save
382 issue3 = issue1.clone
398 issue3 = issue1.clone
383 assert issue3.save
399 assert issue3.save
384
400
385 # 2 is a dupe of 1
401 # 2 is a dupe of 1
386 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
402 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
387 # And 3 is a dupe of 2
403 # And 3 is a dupe of 2
388 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
404 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
389 # And 3 is a dupe of 1 (circular duplicates)
405 # And 3 is a dupe of 1 (circular duplicates)
390 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
406 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
391
407
392 assert issue1.reload.duplicates.include?(issue2)
408 assert issue1.reload.duplicates.include?(issue2)
393
409
394 # Closing issue 1
410 # Closing issue 1
395 issue1.init_journal(User.find(:first), "Closing issue1")
411 issue1.init_journal(User.find(:first), "Closing issue1")
396 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
412 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
397 assert issue1.save
413 assert issue1.save
398 # 2 and 3 should be also closed
414 # 2 and 3 should be also closed
399 assert issue2.reload.closed?
415 assert issue2.reload.closed?
400 assert issue3.reload.closed?
416 assert issue3.reload.closed?
401 end
417 end
402
418
403 def test_should_not_close_duplicated_issue
419 def test_should_not_close_duplicated_issue
404 # Create 3 issues
420 # Create 3 issues
405 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
421 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
406 :status_id => 1, :priority => IssuePriority.all.first,
422 :status_id => 1, :priority => IssuePriority.all.first,
407 :subject => 'Duplicates test', :description => 'Duplicates test')
423 :subject => 'Duplicates test', :description => 'Duplicates test')
408 assert issue1.save
424 assert issue1.save
409 issue2 = issue1.clone
425 issue2 = issue1.clone
410 assert issue2.save
426 assert issue2.save
411
427
412 # 2 is a dupe of 1
428 # 2 is a dupe of 1
413 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
429 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
414 # 2 is a dup of 1 but 1 is not a duplicate of 2
430 # 2 is a dup of 1 but 1 is not a duplicate of 2
415 assert !issue2.reload.duplicates.include?(issue1)
431 assert !issue2.reload.duplicates.include?(issue1)
416
432
417 # Closing issue 2
433 # Closing issue 2
418 issue2.init_journal(User.find(:first), "Closing issue2")
434 issue2.init_journal(User.find(:first), "Closing issue2")
419 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
435 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
420 assert issue2.save
436 assert issue2.save
421 # 1 should not be also closed
437 # 1 should not be also closed
422 assert !issue1.reload.closed?
438 assert !issue1.reload.closed?
423 end
439 end
424
440
425 def test_assignable_versions
441 def test_assignable_versions
426 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
442 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
427 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
443 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
428 end
444 end
429
445
430 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
446 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
431 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
447 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
432 assert !issue.save
448 assert !issue.save
433 assert_not_nil issue.errors[:fixed_version_id]
449 assert_not_nil issue.errors[:fixed_version_id]
434 end
450 end
435
451
436 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
452 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
453 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
438 assert !issue.save
454 assert !issue.save
439 assert_not_nil issue.errors[:fixed_version_id]
455 assert_not_nil issue.errors[:fixed_version_id]
440 end
456 end
441
457
442 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
458 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
443 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
459 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
444 assert issue.save
460 assert issue.save
445 end
461 end
446
462
447 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
463 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
448 issue = Issue.find(11)
464 issue = Issue.find(11)
449 assert_equal 'closed', issue.fixed_version.status
465 assert_equal 'closed', issue.fixed_version.status
450 issue.subject = 'Subject changed'
466 issue.subject = 'Subject changed'
451 assert issue.save
467 assert issue.save
452 end
468 end
453
469
454 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
470 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
455 issue = Issue.find(11)
471 issue = Issue.find(11)
456 issue.status_id = 1
472 issue.status_id = 1
457 assert !issue.save
473 assert !issue.save
458 assert_not_nil issue.errors[:base]
474 assert_not_nil issue.errors[:base]
459 end
475 end
460
476
461 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
477 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
462 issue = Issue.find(11)
478 issue = Issue.find(11)
463 issue.status_id = 1
479 issue.status_id = 1
464 issue.fixed_version_id = 3
480 issue.fixed_version_id = 3
465 assert issue.save
481 assert issue.save
466 end
482 end
467
483
468 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
484 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
469 issue = Issue.find(12)
485 issue = Issue.find(12)
470 assert_equal 'locked', issue.fixed_version.status
486 assert_equal 'locked', issue.fixed_version.status
471 issue.status_id = 1
487 issue.status_id = 1
472 assert issue.save
488 assert issue.save
473 end
489 end
474
490
475 def test_move_to_another_project_with_same_category
491 def test_move_to_another_project_with_same_category
476 issue = Issue.find(1)
492 issue = Issue.find(1)
477 assert issue.move_to_project(Project.find(2))
493 assert issue.move_to_project(Project.find(2))
478 issue.reload
494 issue.reload
479 assert_equal 2, issue.project_id
495 assert_equal 2, issue.project_id
480 # Category changes
496 # Category changes
481 assert_equal 4, issue.category_id
497 assert_equal 4, issue.category_id
482 # Make sure time entries were move to the target project
498 # Make sure time entries were move to the target project
483 assert_equal 2, issue.time_entries.first.project_id
499 assert_equal 2, issue.time_entries.first.project_id
484 end
500 end
485
501
486 def test_move_to_another_project_without_same_category
502 def test_move_to_another_project_without_same_category
487 issue = Issue.find(2)
503 issue = Issue.find(2)
488 assert issue.move_to_project(Project.find(2))
504 assert issue.move_to_project(Project.find(2))
489 issue.reload
505 issue.reload
490 assert_equal 2, issue.project_id
506 assert_equal 2, issue.project_id
491 # Category cleared
507 # Category cleared
492 assert_nil issue.category_id
508 assert_nil issue.category_id
493 end
509 end
494
510
495 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
511 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
496 issue = Issue.find(1)
512 issue = Issue.find(1)
497 issue.update_attribute(:fixed_version_id, 1)
513 issue.update_attribute(:fixed_version_id, 1)
498 assert issue.move_to_project(Project.find(2))
514 assert issue.move_to_project(Project.find(2))
499 issue.reload
515 issue.reload
500 assert_equal 2, issue.project_id
516 assert_equal 2, issue.project_id
501 # Cleared fixed_version
517 # Cleared fixed_version
502 assert_equal nil, issue.fixed_version
518 assert_equal nil, issue.fixed_version
503 end
519 end
504
520
505 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
521 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
506 issue = Issue.find(1)
522 issue = Issue.find(1)
507 issue.update_attribute(:fixed_version_id, 4)
523 issue.update_attribute(:fixed_version_id, 4)
508 assert issue.move_to_project(Project.find(5))
524 assert issue.move_to_project(Project.find(5))
509 issue.reload
525 issue.reload
510 assert_equal 5, issue.project_id
526 assert_equal 5, issue.project_id
511 # Keep fixed_version
527 # Keep fixed_version
512 assert_equal 4, issue.fixed_version_id
528 assert_equal 4, issue.fixed_version_id
513 end
529 end
514
530
515 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
531 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
516 issue = Issue.find(1)
532 issue = Issue.find(1)
517 issue.update_attribute(:fixed_version_id, 1)
533 issue.update_attribute(:fixed_version_id, 1)
518 assert issue.move_to_project(Project.find(5))
534 assert issue.move_to_project(Project.find(5))
519 issue.reload
535 issue.reload
520 assert_equal 5, issue.project_id
536 assert_equal 5, issue.project_id
521 # Cleared fixed_version
537 # Cleared fixed_version
522 assert_equal nil, issue.fixed_version
538 assert_equal nil, issue.fixed_version
523 end
539 end
524
540
525 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
541 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
526 issue = Issue.find(1)
542 issue = Issue.find(1)
527 issue.update_attribute(:fixed_version_id, 7)
543 issue.update_attribute(:fixed_version_id, 7)
528 assert issue.move_to_project(Project.find(2))
544 assert issue.move_to_project(Project.find(2))
529 issue.reload
545 issue.reload
530 assert_equal 2, issue.project_id
546 assert_equal 2, issue.project_id
531 # Keep fixed_version
547 # Keep fixed_version
532 assert_equal 7, issue.fixed_version_id
548 assert_equal 7, issue.fixed_version_id
533 end
549 end
534
550
535 def test_move_to_another_project_with_disabled_tracker
551 def test_move_to_another_project_with_disabled_tracker
536 issue = Issue.find(1)
552 issue = Issue.find(1)
537 target = Project.find(2)
553 target = Project.find(2)
538 target.tracker_ids = [3]
554 target.tracker_ids = [3]
539 target.save
555 target.save
540 assert_equal false, issue.move_to_project(target)
556 assert_equal false, issue.move_to_project(target)
541 issue.reload
557 issue.reload
542 assert_equal 1, issue.project_id
558 assert_equal 1, issue.project_id
543 end
559 end
544
560
545 def test_copy_to_the_same_project
561 def test_copy_to_the_same_project
546 issue = Issue.find(1)
562 issue = Issue.find(1)
547 copy = nil
563 copy = nil
548 assert_difference 'Issue.count' do
564 assert_difference 'Issue.count' do
549 copy = issue.move_to_project(issue.project, nil, :copy => true)
565 copy = issue.move_to_project(issue.project, nil, :copy => true)
550 end
566 end
551 assert_kind_of Issue, copy
567 assert_kind_of Issue, copy
552 assert_equal issue.project, copy.project
568 assert_equal issue.project, copy.project
553 assert_equal "125", copy.custom_value_for(2).value
569 assert_equal "125", copy.custom_value_for(2).value
554 end
570 end
555
571
556 def test_copy_to_another_project_and_tracker
572 def test_copy_to_another_project_and_tracker
557 issue = Issue.find(1)
573 issue = Issue.find(1)
558 copy = nil
574 copy = nil
559 assert_difference 'Issue.count' do
575 assert_difference 'Issue.count' do
560 copy = issue.move_to_project(Project.find(3), Tracker.find(2), :copy => true)
576 copy = issue.move_to_project(Project.find(3), Tracker.find(2), :copy => true)
561 end
577 end
562 copy.reload
578 copy.reload
563 assert_kind_of Issue, copy
579 assert_kind_of Issue, copy
564 assert_equal Project.find(3), copy.project
580 assert_equal Project.find(3), copy.project
565 assert_equal Tracker.find(2), copy.tracker
581 assert_equal Tracker.find(2), copy.tracker
566 # Custom field #2 is not associated with target tracker
582 # Custom field #2 is not associated with target tracker
567 assert_nil copy.custom_value_for(2)
583 assert_nil copy.custom_value_for(2)
568 end
584 end
569
585
570 context "#move_to_project" do
586 context "#move_to_project" do
571 context "as a copy" do
587 context "as a copy" do
572 setup do
588 setup do
573 @issue = Issue.find(1)
589 @issue = Issue.find(1)
574 @copy = nil
590 @copy = nil
575 end
591 end
576
592
577 should "not create a journal" do
593 should "not create a journal" do
578 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
594 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
579 assert_equal 0, @copy.reload.journals.size
595 assert_equal 0, @copy.reload.journals.size
580 end
596 end
581
597
582 should "allow assigned_to changes" do
598 should "allow assigned_to changes" do
583 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
599 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}})
584 assert_equal 3, @copy.assigned_to_id
600 assert_equal 3, @copy.assigned_to_id
585 end
601 end
586
602
587 should "allow status changes" do
603 should "allow status changes" do
588 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}})
604 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}})
589 assert_equal 2, @copy.status_id
605 assert_equal 2, @copy.status_id
590 end
606 end
591
607
592 should "allow start date changes" do
608 should "allow start date changes" do
593 date = Date.today
609 date = Date.today
594 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
610 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
595 assert_equal date, @copy.start_date
611 assert_equal date, @copy.start_date
596 end
612 end
597
613
598 should "allow due date changes" do
614 should "allow due date changes" do
599 date = Date.today
615 date = Date.today
600 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}})
616 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}})
601
617
602 assert_equal date, @copy.due_date
618 assert_equal date, @copy.due_date
603 end
619 end
604
620
605 should "set current user as author" do
621 should "set current user as author" do
606 User.current = User.find(9)
622 User.current = User.find(9)
607 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}})
623 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {}})
608
624
609 assert_equal User.current, @copy.author
625 assert_equal User.current, @copy.author
610 end
626 end
611
627
612 should "keep journal notes" do
628 should "keep journal notes" do
613 date = Date.today
629 date = Date.today
614 notes = "Notes added when copying"
630 notes = "Notes added when copying"
615 User.current = User.find(9)
631 User.current = User.find(9)
616 @issue.init_journal(User.current, notes)
632 @issue.init_journal(User.current, notes)
617 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
633 @copy = @issue.move_to_project(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}})
618
634
619 assert_equal 1, @copy.journals.size
635 assert_equal 1, @copy.journals.size
620 journal = @copy.journals.first
636 journal = @copy.journals.first
621 assert_equal 0, journal.details.size
637 assert_equal 0, journal.details.size
622 assert_equal notes, journal.notes
638 assert_equal notes, journal.notes
623 end
639 end
624 end
640 end
625 end
641 end
626
642
627 def test_recipients_should_not_include_users_that_cannot_view_the_issue
643 def test_recipients_should_not_include_users_that_cannot_view_the_issue
628 issue = Issue.find(12)
644 issue = Issue.find(12)
629 assert issue.recipients.include?(issue.author.mail)
645 assert issue.recipients.include?(issue.author.mail)
630 # move the issue to a private project
646 # move the issue to a private project
631 copy = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true)
647 copy = issue.move_to_project(Project.find(5), Tracker.find(2), :copy => true)
632 # author is not a member of project anymore
648 # author is not a member of project anymore
633 assert !copy.recipients.include?(copy.author.mail)
649 assert !copy.recipients.include?(copy.author.mail)
634 end
650 end
635
651
636 def test_recipients_should_include_the_assigned_group_members
652 def test_recipients_should_include_the_assigned_group_members
637 group_member = User.generate_with_protected!
653 group_member = User.generate_with_protected!
638 group = Group.generate!
654 group = Group.generate!
639 group.users << group_member
655 group.users << group_member
640
656
641 issue = Issue.find(12)
657 issue = Issue.find(12)
642 issue.assigned_to = group
658 issue.assigned_to = group
643 assert issue.recipients.include?(group_member.mail)
659 assert issue.recipients.include?(group_member.mail)
644 end
660 end
645
661
646 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
662 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
647 user = User.find(3)
663 user = User.find(3)
648 issue = Issue.find(9)
664 issue = Issue.find(9)
649 Watcher.create!(:user => user, :watchable => issue)
665 Watcher.create!(:user => user, :watchable => issue)
650 assert issue.watched_by?(user)
666 assert issue.watched_by?(user)
651 assert !issue.watcher_recipients.include?(user.mail)
667 assert !issue.watcher_recipients.include?(user.mail)
652 end
668 end
653
669
654 def test_issue_destroy
670 def test_issue_destroy
655 Issue.find(1).destroy
671 Issue.find(1).destroy
656 assert_nil Issue.find_by_id(1)
672 assert_nil Issue.find_by_id(1)
657 assert_nil TimeEntry.find_by_issue_id(1)
673 assert_nil TimeEntry.find_by_issue_id(1)
658 end
674 end
659
675
660 def test_blocked
676 def test_blocked
661 blocked_issue = Issue.find(9)
677 blocked_issue = Issue.find(9)
662 blocking_issue = Issue.find(10)
678 blocking_issue = Issue.find(10)
663
679
664 assert blocked_issue.blocked?
680 assert blocked_issue.blocked?
665 assert !blocking_issue.blocked?
681 assert !blocking_issue.blocked?
666 end
682 end
667
683
668 def test_blocked_issues_dont_allow_closed_statuses
684 def test_blocked_issues_dont_allow_closed_statuses
669 blocked_issue = Issue.find(9)
685 blocked_issue = Issue.find(9)
670
686
671 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
687 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
672 assert !allowed_statuses.empty?
688 assert !allowed_statuses.empty?
673 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
689 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
674 assert closed_statuses.empty?
690 assert closed_statuses.empty?
675 end
691 end
676
692
677 def test_unblocked_issues_allow_closed_statuses
693 def test_unblocked_issues_allow_closed_statuses
678 blocking_issue = Issue.find(10)
694 blocking_issue = Issue.find(10)
679
695
680 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
696 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
681 assert !allowed_statuses.empty?
697 assert !allowed_statuses.empty?
682 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
698 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
683 assert !closed_statuses.empty?
699 assert !closed_statuses.empty?
684 end
700 end
685
701
686 def test_rescheduling_an_issue_should_reschedule_following_issue
702 def test_rescheduling_an_issue_should_reschedule_following_issue
687 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
703 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
688 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
704 issue2 = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => '-', :start_date => Date.today, :due_date => Date.today + 2)
689 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
705 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
690 assert_equal issue1.due_date + 1, issue2.reload.start_date
706 assert_equal issue1.due_date + 1, issue2.reload.start_date
691
707
692 issue1.due_date = Date.today + 5
708 issue1.due_date = Date.today + 5
693 issue1.save!
709 issue1.save!
694 assert_equal issue1.due_date + 1, issue2.reload.start_date
710 assert_equal issue1.due_date + 1, issue2.reload.start_date
695 end
711 end
696
712
697 def test_overdue
713 def test_overdue
698 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
714 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
699 assert !Issue.new(:due_date => Date.today).overdue?
715 assert !Issue.new(:due_date => Date.today).overdue?
700 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
716 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
701 assert !Issue.new(:due_date => nil).overdue?
717 assert !Issue.new(:due_date => nil).overdue?
702 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
718 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
703 end
719 end
704
720
705 context "#behind_schedule?" do
721 context "#behind_schedule?" do
706 should "be false if the issue has no start_date" do
722 should "be false if the issue has no start_date" do
707 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
723 assert !Issue.new(:start_date => nil, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
708 end
724 end
709
725
710 should "be false if the issue has no end_date" do
726 should "be false if the issue has no end_date" do
711 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
727 assert !Issue.new(:start_date => 1.day.from_now.to_date, :due_date => nil, :done_ratio => 0).behind_schedule?
712 end
728 end
713
729
714 should "be false if the issue has more done than it's calendar time" do
730 should "be false if the issue has more done than it's calendar time" do
715 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
731 assert !Issue.new(:start_date => 50.days.ago.to_date, :due_date => 50.days.from_now.to_date, :done_ratio => 90).behind_schedule?
716 end
732 end
717
733
718 should "be true if the issue hasn't been started at all" do
734 should "be true if the issue hasn't been started at all" do
719 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
735 assert Issue.new(:start_date => 1.day.ago.to_date, :due_date => 1.day.from_now.to_date, :done_ratio => 0).behind_schedule?
720 end
736 end
721
737
722 should "be true if the issue has used more calendar time than it's done ratio" do
738 should "be true if the issue has used more calendar time than it's done ratio" do
723 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
739 assert Issue.new(:start_date => 100.days.ago.to_date, :due_date => Date.today, :done_ratio => 90).behind_schedule?
724 end
740 end
725 end
741 end
726
742
727 context "#assignable_users" do
743 context "#assignable_users" do
728 should "be Users" do
744 should "be Users" do
729 assert_kind_of User, Issue.find(1).assignable_users.first
745 assert_kind_of User, Issue.find(1).assignable_users.first
730 end
746 end
731
747
732 should "include the issue author" do
748 should "include the issue author" do
733 project = Project.find(1)
749 project = Project.find(1)
734 non_project_member = User.generate!
750 non_project_member = User.generate!
735 issue = Issue.generate_for_project!(project, :author => non_project_member)
751 issue = Issue.generate_for_project!(project, :author => non_project_member)
736
752
737 assert issue.assignable_users.include?(non_project_member)
753 assert issue.assignable_users.include?(non_project_member)
738 end
754 end
739
755
740 should "include the current assignee" do
756 should "include the current assignee" do
741 project = Project.find(1)
757 project = Project.find(1)
742 user = User.generate!
758 user = User.generate!
743 issue = Issue.generate_for_project!(project, :assigned_to => user)
759 issue = Issue.generate_for_project!(project, :assigned_to => user)
744 user.lock!
760 user.lock!
745
761
746 assert Issue.find(issue.id).assignable_users.include?(user)
762 assert Issue.find(issue.id).assignable_users.include?(user)
747 end
763 end
748
764
749 should "not show the issue author twice" do
765 should "not show the issue author twice" do
750 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
766 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
751 assert_equal 2, assignable_user_ids.length
767 assert_equal 2, assignable_user_ids.length
752
768
753 assignable_user_ids.each do |user_id|
769 assignable_user_ids.each do |user_id|
754 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
770 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length, "User #{user_id} appears more or less than once"
755 end
771 end
756 end
772 end
757
773
758 context "with issue_group_assignment" do
774 context "with issue_group_assignment" do
759 should "include groups" do
775 should "include groups" do
760 issue = Issue.new(:project => Project.find(2))
776 issue = Issue.new(:project => Project.find(2))
761
777
762 with_settings :issue_group_assignment => '1' do
778 with_settings :issue_group_assignment => '1' do
763 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
779 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
764 assert issue.assignable_users.include?(Group.find(11))
780 assert issue.assignable_users.include?(Group.find(11))
765 end
781 end
766 end
782 end
767 end
783 end
768
784
769 context "without issue_group_assignment" do
785 context "without issue_group_assignment" do
770 should "not include groups" do
786 should "not include groups" do
771 issue = Issue.new(:project => Project.find(2))
787 issue = Issue.new(:project => Project.find(2))
772
788
773 with_settings :issue_group_assignment => '0' do
789 with_settings :issue_group_assignment => '0' do
774 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
790 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
775 assert !issue.assignable_users.include?(Group.find(11))
791 assert !issue.assignable_users.include?(Group.find(11))
776 end
792 end
777 end
793 end
778 end
794 end
779 end
795 end
780
796
781 def test_create_should_send_email_notification
797 def test_create_should_send_email_notification
782 ActionMailer::Base.deliveries.clear
798 ActionMailer::Base.deliveries.clear
783 issue = Issue.new(:project_id => 1, :tracker_id => 1,
799 issue = Issue.new(:project_id => 1, :tracker_id => 1,
784 :author_id => 3, :status_id => 1,
800 :author_id => 3, :status_id => 1,
785 :priority => IssuePriority.all.first,
801 :priority => IssuePriority.all.first,
786 :subject => 'test_create', :estimated_hours => '1:30')
802 :subject => 'test_create', :estimated_hours => '1:30')
787
803
788 assert issue.save
804 assert issue.save
789 assert_equal 1, ActionMailer::Base.deliveries.size
805 assert_equal 1, ActionMailer::Base.deliveries.size
790 end
806 end
791
807
792 def test_stale_issue_should_not_send_email_notification
808 def test_stale_issue_should_not_send_email_notification
793 ActionMailer::Base.deliveries.clear
809 ActionMailer::Base.deliveries.clear
794 issue = Issue.find(1)
810 issue = Issue.find(1)
795 stale = Issue.find(1)
811 stale = Issue.find(1)
796
812
797 issue.init_journal(User.find(1))
813 issue.init_journal(User.find(1))
798 issue.subject = 'Subjet update'
814 issue.subject = 'Subjet update'
799 assert issue.save
815 assert issue.save
800 assert_equal 1, ActionMailer::Base.deliveries.size
816 assert_equal 1, ActionMailer::Base.deliveries.size
801 ActionMailer::Base.deliveries.clear
817 ActionMailer::Base.deliveries.clear
802
818
803 stale.init_journal(User.find(1))
819 stale.init_journal(User.find(1))
804 stale.subject = 'Another subjet update'
820 stale.subject = 'Another subjet update'
805 assert_raise ActiveRecord::StaleObjectError do
821 assert_raise ActiveRecord::StaleObjectError do
806 stale.save
822 stale.save
807 end
823 end
808 assert ActionMailer::Base.deliveries.empty?
824 assert ActionMailer::Base.deliveries.empty?
809 end
825 end
810
826
811 def test_journalized_description
827 def test_journalized_description
812 IssueCustomField.delete_all
828 IssueCustomField.delete_all
813
829
814 i = Issue.first
830 i = Issue.first
815 old_description = i.description
831 old_description = i.description
816 new_description = "This is the new description"
832 new_description = "This is the new description"
817
833
818 i.init_journal(User.find(2))
834 i.init_journal(User.find(2))
819 i.description = new_description
835 i.description = new_description
820 assert_difference 'Journal.count', 1 do
836 assert_difference 'Journal.count', 1 do
821 assert_difference 'JournalDetail.count', 1 do
837 assert_difference 'JournalDetail.count', 1 do
822 i.save!
838 i.save!
823 end
839 end
824 end
840 end
825
841
826 detail = JournalDetail.first(:order => 'id DESC')
842 detail = JournalDetail.first(:order => 'id DESC')
827 assert_equal i, detail.journal.journalized
843 assert_equal i, detail.journal.journalized
828 assert_equal 'attr', detail.property
844 assert_equal 'attr', detail.property
829 assert_equal 'description', detail.prop_key
845 assert_equal 'description', detail.prop_key
830 assert_equal old_description, detail.old_value
846 assert_equal old_description, detail.old_value
831 assert_equal new_description, detail.value
847 assert_equal new_description, detail.value
832 end
848 end
833
849
834 def test_blank_descriptions_should_not_be_journalized
850 def test_blank_descriptions_should_not_be_journalized
835 IssueCustomField.delete_all
851 IssueCustomField.delete_all
836 Issue.update_all("description = NULL", "id=1")
852 Issue.update_all("description = NULL", "id=1")
837
853
838 i = Issue.find(1)
854 i = Issue.find(1)
839 i.init_journal(User.find(2))
855 i.init_journal(User.find(2))
840 i.subject = "blank description"
856 i.subject = "blank description"
841 i.description = "\r\n"
857 i.description = "\r\n"
842
858
843 assert_difference 'Journal.count', 1 do
859 assert_difference 'Journal.count', 1 do
844 assert_difference 'JournalDetail.count', 1 do
860 assert_difference 'JournalDetail.count', 1 do
845 i.save!
861 i.save!
846 end
862 end
847 end
863 end
848 end
864 end
849
865
850 def test_description_eol_should_be_normalized
866 def test_description_eol_should_be_normalized
851 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
867 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
852 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
868 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
853 end
869 end
854
870
855 def test_saving_twice_should_not_duplicate_journal_details
871 def test_saving_twice_should_not_duplicate_journal_details
856 i = Issue.find(:first)
872 i = Issue.find(:first)
857 i.init_journal(User.find(2), 'Some notes')
873 i.init_journal(User.find(2), 'Some notes')
858 # initial changes
874 # initial changes
859 i.subject = 'New subject'
875 i.subject = 'New subject'
860 i.done_ratio = i.done_ratio + 10
876 i.done_ratio = i.done_ratio + 10
861 assert_difference 'Journal.count' do
877 assert_difference 'Journal.count' do
862 assert i.save
878 assert i.save
863 end
879 end
864 # 1 more change
880 # 1 more change
865 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
881 i.priority = IssuePriority.find(:first, :conditions => ["id <> ?", i.priority_id])
866 assert_no_difference 'Journal.count' do
882 assert_no_difference 'Journal.count' do
867 assert_difference 'JournalDetail.count', 1 do
883 assert_difference 'JournalDetail.count', 1 do
868 i.save
884 i.save
869 end
885 end
870 end
886 end
871 # no more change
887 # no more change
872 assert_no_difference 'Journal.count' do
888 assert_no_difference 'Journal.count' do
873 assert_no_difference 'JournalDetail.count' do
889 assert_no_difference 'JournalDetail.count' do
874 i.save
890 i.save
875 end
891 end
876 end
892 end
877 end
893 end
878
894
879 def test_all_dependent_issues
895 def test_all_dependent_issues
880 IssueRelation.delete_all
896 IssueRelation.delete_all
881 assert IssueRelation.create!(:issue_from => Issue.find(1),
897 assert IssueRelation.create!(:issue_from => Issue.find(1),
882 :issue_to => Issue.find(2),
898 :issue_to => Issue.find(2),
883 :relation_type => IssueRelation::TYPE_PRECEDES)
899 :relation_type => IssueRelation::TYPE_PRECEDES)
884 assert IssueRelation.create!(:issue_from => Issue.find(2),
900 assert IssueRelation.create!(:issue_from => Issue.find(2),
885 :issue_to => Issue.find(3),
901 :issue_to => Issue.find(3),
886 :relation_type => IssueRelation::TYPE_PRECEDES)
902 :relation_type => IssueRelation::TYPE_PRECEDES)
887 assert IssueRelation.create!(:issue_from => Issue.find(3),
903 assert IssueRelation.create!(:issue_from => Issue.find(3),
888 :issue_to => Issue.find(8),
904 :issue_to => Issue.find(8),
889 :relation_type => IssueRelation::TYPE_PRECEDES)
905 :relation_type => IssueRelation::TYPE_PRECEDES)
890
906
891 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
907 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
892 end
908 end
893
909
894 def test_all_dependent_issues_with_persistent_circular_dependency
910 def test_all_dependent_issues_with_persistent_circular_dependency
895 IssueRelation.delete_all
911 IssueRelation.delete_all
896 assert IssueRelation.create!(:issue_from => Issue.find(1),
912 assert IssueRelation.create!(:issue_from => Issue.find(1),
897 :issue_to => Issue.find(2),
913 :issue_to => Issue.find(2),
898 :relation_type => IssueRelation::TYPE_PRECEDES)
914 :relation_type => IssueRelation::TYPE_PRECEDES)
899 assert IssueRelation.create!(:issue_from => Issue.find(2),
915 assert IssueRelation.create!(:issue_from => Issue.find(2),
900 :issue_to => Issue.find(3),
916 :issue_to => Issue.find(3),
901 :relation_type => IssueRelation::TYPE_PRECEDES)
917 :relation_type => IssueRelation::TYPE_PRECEDES)
902 # Validation skipping
918 # Validation skipping
903 assert IssueRelation.new(:issue_from => Issue.find(3),
919 assert IssueRelation.new(:issue_from => Issue.find(3),
904 :issue_to => Issue.find(1),
920 :issue_to => Issue.find(1),
905 :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
921 :relation_type => IssueRelation::TYPE_PRECEDES).save(false)
906
922
907 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
923 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
908 end
924 end
909
925
910 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
926 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
911 IssueRelation.delete_all
927 IssueRelation.delete_all
912 assert IssueRelation.create!(:issue_from => Issue.find(1),
928 assert IssueRelation.create!(:issue_from => Issue.find(1),
913 :issue_to => Issue.find(2),
929 :issue_to => Issue.find(2),
914 :relation_type => IssueRelation::TYPE_RELATES)
930 :relation_type => IssueRelation::TYPE_RELATES)
915 assert IssueRelation.create!(:issue_from => Issue.find(2),
931 assert IssueRelation.create!(:issue_from => Issue.find(2),
916 :issue_to => Issue.find(3),
932 :issue_to => Issue.find(3),
917 :relation_type => IssueRelation::TYPE_RELATES)
933 :relation_type => IssueRelation::TYPE_RELATES)
918 assert IssueRelation.create!(:issue_from => Issue.find(3),
934 assert IssueRelation.create!(:issue_from => Issue.find(3),
919 :issue_to => Issue.find(8),
935 :issue_to => Issue.find(8),
920 :relation_type => IssueRelation::TYPE_RELATES)
936 :relation_type => IssueRelation::TYPE_RELATES)
921 # Validation skipping
937 # Validation skipping
922 assert IssueRelation.new(:issue_from => Issue.find(8),
938 assert IssueRelation.new(:issue_from => Issue.find(8),
923 :issue_to => Issue.find(2),
939 :issue_to => Issue.find(2),
924 :relation_type => IssueRelation::TYPE_RELATES).save(false)
940 :relation_type => IssueRelation::TYPE_RELATES).save(false)
925 assert IssueRelation.new(:issue_from => Issue.find(3),
941 assert IssueRelation.new(:issue_from => Issue.find(3),
926 :issue_to => Issue.find(1),
942 :issue_to => Issue.find(1),
927 :relation_type => IssueRelation::TYPE_RELATES).save(false)
943 :relation_type => IssueRelation::TYPE_RELATES).save(false)
928
944
929 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
945 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
930 end
946 end
931
947
932 context "#done_ratio" do
948 context "#done_ratio" do
933 setup do
949 setup do
934 @issue = Issue.find(1)
950 @issue = Issue.find(1)
935 @issue_status = IssueStatus.find(1)
951 @issue_status = IssueStatus.find(1)
936 @issue_status.update_attribute(:default_done_ratio, 50)
952 @issue_status.update_attribute(:default_done_ratio, 50)
937 @issue2 = Issue.find(2)
953 @issue2 = Issue.find(2)
938 @issue_status2 = IssueStatus.find(2)
954 @issue_status2 = IssueStatus.find(2)
939 @issue_status2.update_attribute(:default_done_ratio, 0)
955 @issue_status2.update_attribute(:default_done_ratio, 0)
940 end
956 end
941
957
942 context "with Setting.issue_done_ratio using the issue_field" do
958 context "with Setting.issue_done_ratio using the issue_field" do
943 setup do
959 setup do
944 Setting.issue_done_ratio = 'issue_field'
960 Setting.issue_done_ratio = 'issue_field'
945 end
961 end
946
962
947 should "read the issue's field" do
963 should "read the issue's field" do
948 assert_equal 0, @issue.done_ratio
964 assert_equal 0, @issue.done_ratio
949 assert_equal 30, @issue2.done_ratio
965 assert_equal 30, @issue2.done_ratio
950 end
966 end
951 end
967 end
952
968
953 context "with Setting.issue_done_ratio using the issue_status" do
969 context "with Setting.issue_done_ratio using the issue_status" do
954 setup do
970 setup do
955 Setting.issue_done_ratio = 'issue_status'
971 Setting.issue_done_ratio = 'issue_status'
956 end
972 end
957
973
958 should "read the Issue Status's default done ratio" do
974 should "read the Issue Status's default done ratio" do
959 assert_equal 50, @issue.done_ratio
975 assert_equal 50, @issue.done_ratio
960 assert_equal 0, @issue2.done_ratio
976 assert_equal 0, @issue2.done_ratio
961 end
977 end
962 end
978 end
963 end
979 end
964
980
965 context "#update_done_ratio_from_issue_status" do
981 context "#update_done_ratio_from_issue_status" do
966 setup do
982 setup do
967 @issue = Issue.find(1)
983 @issue = Issue.find(1)
968 @issue_status = IssueStatus.find(1)
984 @issue_status = IssueStatus.find(1)
969 @issue_status.update_attribute(:default_done_ratio, 50)
985 @issue_status.update_attribute(:default_done_ratio, 50)
970 @issue2 = Issue.find(2)
986 @issue2 = Issue.find(2)
971 @issue_status2 = IssueStatus.find(2)
987 @issue_status2 = IssueStatus.find(2)
972 @issue_status2.update_attribute(:default_done_ratio, 0)
988 @issue_status2.update_attribute(:default_done_ratio, 0)
973 end
989 end
974
990
975 context "with Setting.issue_done_ratio using the issue_field" do
991 context "with Setting.issue_done_ratio using the issue_field" do
976 setup do
992 setup do
977 Setting.issue_done_ratio = 'issue_field'
993 Setting.issue_done_ratio = 'issue_field'
978 end
994 end
979
995
980 should "not change the issue" do
996 should "not change the issue" do
981 @issue.update_done_ratio_from_issue_status
997 @issue.update_done_ratio_from_issue_status
982 @issue2.update_done_ratio_from_issue_status
998 @issue2.update_done_ratio_from_issue_status
983
999
984 assert_equal 0, @issue.read_attribute(:done_ratio)
1000 assert_equal 0, @issue.read_attribute(:done_ratio)
985 assert_equal 30, @issue2.read_attribute(:done_ratio)
1001 assert_equal 30, @issue2.read_attribute(:done_ratio)
986 end
1002 end
987 end
1003 end
988
1004
989 context "with Setting.issue_done_ratio using the issue_status" do
1005 context "with Setting.issue_done_ratio using the issue_status" do
990 setup do
1006 setup do
991 Setting.issue_done_ratio = 'issue_status'
1007 Setting.issue_done_ratio = 'issue_status'
992 end
1008 end
993
1009
994 should "change the issue's done ratio" do
1010 should "change the issue's done ratio" do
995 @issue.update_done_ratio_from_issue_status
1011 @issue.update_done_ratio_from_issue_status
996 @issue2.update_done_ratio_from_issue_status
1012 @issue2.update_done_ratio_from_issue_status
997
1013
998 assert_equal 50, @issue.read_attribute(:done_ratio)
1014 assert_equal 50, @issue.read_attribute(:done_ratio)
999 assert_equal 0, @issue2.read_attribute(:done_ratio)
1015 assert_equal 0, @issue2.read_attribute(:done_ratio)
1000 end
1016 end
1001 end
1017 end
1002 end
1018 end
1003
1019
1004 test "#by_tracker" do
1020 test "#by_tracker" do
1005 User.current = User.anonymous
1021 User.current = User.anonymous
1006 groups = Issue.by_tracker(Project.find(1))
1022 groups = Issue.by_tracker(Project.find(1))
1007 assert_equal 3, groups.size
1023 assert_equal 3, groups.size
1008 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1024 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1009 end
1025 end
1010
1026
1011 test "#by_version" do
1027 test "#by_version" do
1012 User.current = User.anonymous
1028 User.current = User.anonymous
1013 groups = Issue.by_version(Project.find(1))
1029 groups = Issue.by_version(Project.find(1))
1014 assert_equal 3, groups.size
1030 assert_equal 3, groups.size
1015 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1031 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1016 end
1032 end
1017
1033
1018 test "#by_priority" do
1034 test "#by_priority" do
1019 User.current = User.anonymous
1035 User.current = User.anonymous
1020 groups = Issue.by_priority(Project.find(1))
1036 groups = Issue.by_priority(Project.find(1))
1021 assert_equal 4, groups.size
1037 assert_equal 4, groups.size
1022 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1038 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1023 end
1039 end
1024
1040
1025 test "#by_category" do
1041 test "#by_category" do
1026 User.current = User.anonymous
1042 User.current = User.anonymous
1027 groups = Issue.by_category(Project.find(1))
1043 groups = Issue.by_category(Project.find(1))
1028 assert_equal 2, groups.size
1044 assert_equal 2, groups.size
1029 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1045 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1030 end
1046 end
1031
1047
1032 test "#by_assigned_to" do
1048 test "#by_assigned_to" do
1033 User.current = User.anonymous
1049 User.current = User.anonymous
1034 groups = Issue.by_assigned_to(Project.find(1))
1050 groups = Issue.by_assigned_to(Project.find(1))
1035 assert_equal 2, groups.size
1051 assert_equal 2, groups.size
1036 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1052 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1037 end
1053 end
1038
1054
1039 test "#by_author" do
1055 test "#by_author" do
1040 User.current = User.anonymous
1056 User.current = User.anonymous
1041 groups = Issue.by_author(Project.find(1))
1057 groups = Issue.by_author(Project.find(1))
1042 assert_equal 4, groups.size
1058 assert_equal 4, groups.size
1043 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1059 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1044 end
1060 end
1045
1061
1046 test "#by_subproject" do
1062 test "#by_subproject" do
1047 User.current = User.anonymous
1063 User.current = User.anonymous
1048 groups = Issue.by_subproject(Project.find(1))
1064 groups = Issue.by_subproject(Project.find(1))
1049 # Private descendant not visible
1065 # Private descendant not visible
1050 assert_equal 1, groups.size
1066 assert_equal 1, groups.size
1051 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1067 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
1052 end
1068 end
1053
1069
1054 context ".allowed_target_projects_on_move" do
1070 context ".allowed_target_projects_on_move" do
1055 should "return all active projects for admin users" do
1071 should "return all active projects for admin users" do
1056 User.current = User.find(1)
1072 User.current = User.find(1)
1057 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1073 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1058 end
1074 end
1059
1075
1060 should "return allowed projects for non admin users" do
1076 should "return allowed projects for non admin users" do
1061 User.current = User.find(2)
1077 User.current = User.find(2)
1062 Role.non_member.remove_permission! :move_issues
1078 Role.non_member.remove_permission! :move_issues
1063 assert_equal 3, Issue.allowed_target_projects_on_move.size
1079 assert_equal 3, Issue.allowed_target_projects_on_move.size
1064
1080
1065 Role.non_member.add_permission! :move_issues
1081 Role.non_member.add_permission! :move_issues
1066 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1082 assert_equal Project.active.count, Issue.allowed_target_projects_on_move.size
1067 end
1083 end
1068 end
1084 end
1069
1085
1070 def test_recently_updated_with_limit_scopes
1086 def test_recently_updated_with_limit_scopes
1071 #should return the last updated issue
1087 #should return the last updated issue
1072 assert_equal 1, Issue.recently_updated.with_limit(1).length
1088 assert_equal 1, Issue.recently_updated.with_limit(1).length
1073 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1089 assert_equal Issue.find(:first, :order => "updated_on DESC"), Issue.recently_updated.with_limit(1).first
1074 end
1090 end
1075
1091
1076 def test_on_active_projects_scope
1092 def test_on_active_projects_scope
1077 assert Project.find(2).archive
1093 assert Project.find(2).archive
1078
1094
1079 before = Issue.on_active_project.length
1095 before = Issue.on_active_project.length
1080 # test inclusion to results
1096 # test inclusion to results
1081 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1097 issue = Issue.generate_for_project!(Project.find(1), :tracker => Project.find(2).trackers.first)
1082 assert_equal before + 1, Issue.on_active_project.length
1098 assert_equal before + 1, Issue.on_active_project.length
1083
1099
1084 # Move to an archived project
1100 # Move to an archived project
1085 issue.project = Project.find(2)
1101 issue.project = Project.find(2)
1086 assert issue.save
1102 assert issue.save
1087 assert_equal before, Issue.on_active_project.length
1103 assert_equal before, Issue.on_active_project.length
1088 end
1104 end
1089
1105
1090 context "Issue#recipients" do
1106 context "Issue#recipients" do
1091 setup do
1107 setup do
1092 @project = Project.find(1)
1108 @project = Project.find(1)
1093 @author = User.generate_with_protected!
1109 @author = User.generate_with_protected!
1094 @assignee = User.generate_with_protected!
1110 @assignee = User.generate_with_protected!
1095 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1111 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
1096 end
1112 end
1097
1113
1098 should "include project recipients" do
1114 should "include project recipients" do
1099 assert @project.recipients.present?
1115 assert @project.recipients.present?
1100 @project.recipients.each do |project_recipient|
1116 @project.recipients.each do |project_recipient|
1101 assert @issue.recipients.include?(project_recipient)
1117 assert @issue.recipients.include?(project_recipient)
1102 end
1118 end
1103 end
1119 end
1104
1120
1105 should "include the author if the author is active" do
1121 should "include the author if the author is active" do
1106 assert @issue.author, "No author set for Issue"
1122 assert @issue.author, "No author set for Issue"
1107 assert @issue.recipients.include?(@issue.author.mail)
1123 assert @issue.recipients.include?(@issue.author.mail)
1108 end
1124 end
1109
1125
1110 should "include the assigned to user if the assigned to user is active" do
1126 should "include the assigned to user if the assigned to user is active" do
1111 assert @issue.assigned_to, "No assigned_to set for Issue"
1127 assert @issue.assigned_to, "No assigned_to set for Issue"
1112 assert @issue.recipients.include?(@issue.assigned_to.mail)
1128 assert @issue.recipients.include?(@issue.assigned_to.mail)
1113 end
1129 end
1114
1130
1115 should "not include users who opt out of all email" do
1131 should "not include users who opt out of all email" do
1116 @author.update_attribute(:mail_notification, :none)
1132 @author.update_attribute(:mail_notification, :none)
1117
1133
1118 assert !@issue.recipients.include?(@issue.author.mail)
1134 assert !@issue.recipients.include?(@issue.author.mail)
1119 end
1135 end
1120
1136
1121 should "not include the issue author if they are only notified of assigned issues" do
1137 should "not include the issue author if they are only notified of assigned issues" do
1122 @author.update_attribute(:mail_notification, :only_assigned)
1138 @author.update_attribute(:mail_notification, :only_assigned)
1123
1139
1124 assert !@issue.recipients.include?(@issue.author.mail)
1140 assert !@issue.recipients.include?(@issue.author.mail)
1125 end
1141 end
1126
1142
1127 should "not include the assigned user if they are only notified of owned issues" do
1143 should "not include the assigned user if they are only notified of owned issues" do
1128 @assignee.update_attribute(:mail_notification, :only_owner)
1144 @assignee.update_attribute(:mail_notification, :only_owner)
1129
1145
1130 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1146 assert !@issue.recipients.include?(@issue.assigned_to.mail)
1131 end
1147 end
1132
1148
1133 end
1149 end
1134 end
1150 end
@@ -1,112 +1,112
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 module Redmine
18 module Redmine
19 module Acts
19 module Acts
20 module Customizable
20 module Customizable
21 def self.included(base)
21 def self.included(base)
22 base.extend ClassMethods
22 base.extend ClassMethods
23 end
23 end
24
24
25 module ClassMethods
25 module ClassMethods
26 def acts_as_customizable(options = {})
26 def acts_as_customizable(options = {})
27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
27 return if self.included_modules.include?(Redmine::Acts::Customizable::InstanceMethods)
28 cattr_accessor :customizable_options
28 cattr_accessor :customizable_options
29 self.customizable_options = options
29 self.customizable_options = options
30 has_many :custom_values, :as => :customized,
30 has_many :custom_values, :as => :customized,
31 :include => :custom_field,
31 :include => :custom_field,
32 :order => "#{CustomField.table_name}.position",
32 :order => "#{CustomField.table_name}.position",
33 :dependent => :delete_all
33 :dependent => :delete_all
34 before_validation { |customized| customized.custom_field_values if customized.new_record? }
34 before_validation { |customized| customized.custom_field_values if customized.new_record? }
35 # Trigger validation only if custom values were changed
35 # Trigger validation only if custom values were changed
36 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
36 validates_associated :custom_values, :on => :update, :if => Proc.new { |customized| customized.custom_field_values_changed? }
37 send :include, Redmine::Acts::Customizable::InstanceMethods
37 send :include, Redmine::Acts::Customizable::InstanceMethods
38 # Save custom values when saving the customized object
38 # Save custom values when saving the customized object
39 after_save :save_custom_field_values
39 after_save :save_custom_field_values
40 end
40 end
41 end
41 end
42
42
43 module InstanceMethods
43 module InstanceMethods
44 def self.included(base)
44 def self.included(base)
45 base.extend ClassMethods
45 base.extend ClassMethods
46 end
46 end
47
47
48 def available_custom_fields
48 def available_custom_fields
49 CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'",
49 CustomField.find(:all, :conditions => "type = '#{self.class.name}CustomField'",
50 :order => 'position')
50 :order => 'position')
51 end
51 end
52
52
53 # Sets the values of the object's custom fields
53 # Sets the values of the object's custom fields
54 # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}]
54 # values is an array like [{'id' => 1, 'value' => 'foo'}, {'id' => 2, 'value' => 'bar'}]
55 def custom_fields=(values)
55 def custom_fields=(values)
56 values_to_hash = values.inject({}) do |hash, v|
56 values_to_hash = values.inject({}) do |hash, v|
57 v = v.stringify_keys
57 v = v.stringify_keys
58 if v['id'] && v.has_key?('value')
58 if v['id'] && v.has_key?('value')
59 hash[v['id']] = v['value']
59 hash[v['id']] = v['value']
60 end
60 end
61 hash
61 hash
62 end
62 end
63 self.custom_field_values = values_to_hash
63 self.custom_field_values = values_to_hash
64 end
64 end
65
65
66 # Sets the values of the object's custom fields
66 # Sets the values of the object's custom fields
67 # values is a hash like {'1' => 'foo', 2 => 'bar'}
67 # values is a hash like {'1' => 'foo', 2 => 'bar'}
68 def custom_field_values=(values)
68 def custom_field_values=(values)
69 @custom_field_values_changed = true
69 @custom_field_values_changed = true
70 values = values.stringify_keys
70 values = values.stringify_keys
71 custom_field_values.each do |custom_value|
71 custom_field_values.each do |custom_value|
72 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
72 custom_value.value = values[custom_value.custom_field_id.to_s] if values.has_key?(custom_value.custom_field_id.to_s)
73 end if values.is_a?(Hash)
73 end if values.is_a?(Hash)
74 self.custom_values = custom_field_values
75 end
74 end
76
75
77 def custom_field_values
76 def custom_field_values
78 @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) }
77 @custom_field_values ||= available_custom_fields.collect { |x| custom_values.detect { |v| v.custom_field == x } || custom_values.build(:customized => self, :custom_field => x, :value => nil) }
79 end
78 end
80
79
81 def visible_custom_field_values
80 def visible_custom_field_values
82 custom_field_values.select(&:visible?)
81 custom_field_values.select(&:visible?)
83 end
82 end
84
83
85 def custom_field_values_changed?
84 def custom_field_values_changed?
86 @custom_field_values_changed == true
85 @custom_field_values_changed == true
87 end
86 end
88
87
89 def custom_value_for(c)
88 def custom_value_for(c)
90 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
89 field_id = (c.is_a?(CustomField) ? c.id : c.to_i)
91 custom_values.detect {|v| v.custom_field_id == field_id }
90 custom_values.detect {|v| v.custom_field_id == field_id }
92 end
91 end
93
92
94 def save_custom_field_values
93 def save_custom_field_values
94 self.custom_values = custom_field_values
95 custom_field_values.each(&:save)
95 custom_field_values.each(&:save)
96 @custom_field_values_changed = false
96 @custom_field_values_changed = false
97 @custom_field_values = nil
97 @custom_field_values = nil
98 end
98 end
99
99
100 def reset_custom_values!
100 def reset_custom_values!
101 @custom_field_values = nil
101 @custom_field_values = nil
102 @custom_field_values_changed = true
102 @custom_field_values_changed = true
103 values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
103 values = custom_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
104 custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)}
104 custom_values.each {|cv| cv.destroy unless custom_field_values.include?(cv)}
105 end
105 end
106
106
107 module ClassMethods
107 module ClassMethods
108 end
108 end
109 end
109 end
110 end
110 end
111 end
111 end
112 end
112 end
General Comments 0
You need to be logged in to leave comments. Login now