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