##// END OF EJS Templates
Fixed that members without view issues permission are able to list issues on public projects if the non member role has the permission (#20206)....
Jean-Philippe Lang -
r14068:1def32c4dda9
parent child
Show More
@@ -1,1007 +1,1011
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Project < ActiveRecord::Base
18 class Project < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 include Redmine::NestedSet::ProjectNestedSet
20 include Redmine::NestedSet::ProjectNestedSet
21
21
22 # Project statuses
22 # Project statuses
23 STATUS_ACTIVE = 1
23 STATUS_ACTIVE = 1
24 STATUS_CLOSED = 5
24 STATUS_CLOSED = 5
25 STATUS_ARCHIVED = 9
25 STATUS_ARCHIVED = 9
26
26
27 # Maximum length for project identifiers
27 # Maximum length for project identifiers
28 IDENTIFIER_MAX_LENGTH = 100
28 IDENTIFIER_MAX_LENGTH = 100
29
29
30 # Specific overridden Activities
30 # Specific overridden Activities
31 has_many :time_entry_activities
31 has_many :time_entry_activities
32 has_many :memberships, :class_name => 'Member', :inverse_of => :project
32 has_many :memberships, :class_name => 'Member', :inverse_of => :project
33 # Memberships of active users only
33 # Memberships of active users only
34 has_many :members,
34 has_many :members,
35 lambda { joins(:principal).where(:users => {:type => 'User', :status => Principal::STATUS_ACTIVE}) }
35 lambda { joins(:principal).where(:users => {:type => 'User', :status => Principal::STATUS_ACTIVE}) }
36 has_many :enabled_modules, :dependent => :delete_all
36 has_many :enabled_modules, :dependent => :delete_all
37 has_and_belongs_to_many :trackers, lambda {order(:position)}
37 has_and_belongs_to_many :trackers, lambda {order(:position)}
38 has_many :issues, :dependent => :destroy
38 has_many :issues, :dependent => :destroy
39 has_many :issue_changes, :through => :issues, :source => :journals
39 has_many :issue_changes, :through => :issues, :source => :journals
40 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
40 has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy
41 has_many :time_entries, :dependent => :destroy
41 has_many :time_entries, :dependent => :destroy
42 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
42 has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all
43 has_many :documents, :dependent => :destroy
43 has_many :documents, :dependent => :destroy
44 has_many :news, lambda {includes(:author)}, :dependent => :destroy
44 has_many :news, lambda {includes(:author)}, :dependent => :destroy
45 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
45 has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all
46 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
46 has_many :boards, lambda {order("position ASC")}, :dependent => :destroy
47 has_one :repository, lambda {where(["is_default = ?", true])}
47 has_one :repository, lambda {where(["is_default = ?", true])}
48 has_many :repositories, :dependent => :destroy
48 has_many :repositories, :dependent => :destroy
49 has_many :changesets, :through => :repository
49 has_many :changesets, :through => :repository
50 has_one :wiki, :dependent => :destroy
50 has_one :wiki, :dependent => :destroy
51 # Custom field for the project issues
51 # Custom field for the project issues
52 has_and_belongs_to_many :issue_custom_fields,
52 has_and_belongs_to_many :issue_custom_fields,
53 lambda {order("#{CustomField.table_name}.position")},
53 lambda {order("#{CustomField.table_name}.position")},
54 :class_name => 'IssueCustomField',
54 :class_name => 'IssueCustomField',
55 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
55 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
56 :association_foreign_key => 'custom_field_id'
56 :association_foreign_key => 'custom_field_id'
57
57
58 acts_as_attachable :view_permission => :view_files,
58 acts_as_attachable :view_permission => :view_files,
59 :edit_permission => :manage_files,
59 :edit_permission => :manage_files,
60 :delete_permission => :manage_files
60 :delete_permission => :manage_files
61
61
62 acts_as_customizable
62 acts_as_customizable
63 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
63 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => "#{Project.table_name}.id", :permission => nil
64 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
64 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
65 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
65 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
66 :author => nil
66 :author => nil
67
67
68 attr_protected :status
68 attr_protected :status
69
69
70 validates_presence_of :name, :identifier
70 validates_presence_of :name, :identifier
71 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
71 validates_uniqueness_of :identifier, :if => Proc.new {|p| p.identifier_changed?}
72 validates_length_of :name, :maximum => 255
72 validates_length_of :name, :maximum => 255
73 validates_length_of :homepage, :maximum => 255
73 validates_length_of :homepage, :maximum => 255
74 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
74 validates_length_of :identifier, :in => 1..IDENTIFIER_MAX_LENGTH
75 # downcase letters, digits, dashes but not digits only
75 # downcase letters, digits, dashes but not digits only
76 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
76 validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :if => Proc.new { |p| p.identifier_changed? }
77 # reserved words
77 # reserved words
78 validates_exclusion_of :identifier, :in => %w( new )
78 validates_exclusion_of :identifier, :in => %w( new )
79 validate :validate_parent
79 validate :validate_parent
80
80
81 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
81 after_save :update_inherited_members, :if => Proc.new {|project| project.inherit_members_changed?}
82 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
82 after_save :remove_inherited_member_roles, :add_inherited_member_roles, :if => Proc.new {|project| project.parent_id_changed?}
83 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
83 after_update :update_versions_from_hierarchy_change, :if => Proc.new {|project| project.parent_id_changed?}
84 before_destroy :delete_all_members
84 before_destroy :delete_all_members
85
85
86 scope :has_module, lambda {|mod|
86 scope :has_module, lambda {|mod|
87 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
87 where("#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s)
88 }
88 }
89 scope :active, lambda { where(:status => STATUS_ACTIVE) }
89 scope :active, lambda { where(:status => STATUS_ACTIVE) }
90 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
90 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
91 scope :all_public, lambda { where(:is_public => true) }
91 scope :all_public, lambda { where(:is_public => true) }
92 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
92 scope :visible, lambda {|*args| where(Project.visible_condition(args.shift || User.current, *args)) }
93 scope :allowed_to, lambda {|*args|
93 scope :allowed_to, lambda {|*args|
94 user = User.current
94 user = User.current
95 permission = nil
95 permission = nil
96 if args.first.is_a?(Symbol)
96 if args.first.is_a?(Symbol)
97 permission = args.shift
97 permission = args.shift
98 else
98 else
99 user = args.shift
99 user = args.shift
100 permission = args.shift
100 permission = args.shift
101 end
101 end
102 where(Project.allowed_to_condition(user, permission, *args))
102 where(Project.allowed_to_condition(user, permission, *args))
103 }
103 }
104 scope :like, lambda {|arg|
104 scope :like, lambda {|arg|
105 if arg.blank?
105 if arg.blank?
106 where(nil)
106 where(nil)
107 else
107 else
108 pattern = "%#{arg.to_s.strip.downcase}%"
108 pattern = "%#{arg.to_s.strip.downcase}%"
109 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
109 where("LOWER(identifier) LIKE :p OR LOWER(name) LIKE :p", :p => pattern)
110 end
110 end
111 }
111 }
112 scope :sorted, lambda {order(:lft)}
112 scope :sorted, lambda {order(:lft)}
113
113
114 def initialize(attributes=nil, *args)
114 def initialize(attributes=nil, *args)
115 super
115 super
116
116
117 initialized = (attributes || {}).stringify_keys
117 initialized = (attributes || {}).stringify_keys
118 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
118 if !initialized.key?('identifier') && Setting.sequential_project_identifiers?
119 self.identifier = Project.next_identifier
119 self.identifier = Project.next_identifier
120 end
120 end
121 if !initialized.key?('is_public')
121 if !initialized.key?('is_public')
122 self.is_public = Setting.default_projects_public?
122 self.is_public = Setting.default_projects_public?
123 end
123 end
124 if !initialized.key?('enabled_module_names')
124 if !initialized.key?('enabled_module_names')
125 self.enabled_module_names = Setting.default_projects_modules
125 self.enabled_module_names = Setting.default_projects_modules
126 end
126 end
127 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
127 if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
128 default = Setting.default_projects_tracker_ids
128 default = Setting.default_projects_tracker_ids
129 if default.is_a?(Array)
129 if default.is_a?(Array)
130 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
130 self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a
131 else
131 else
132 self.trackers = Tracker.sorted.to_a
132 self.trackers = Tracker.sorted.to_a
133 end
133 end
134 end
134 end
135 end
135 end
136
136
137 def identifier=(identifier)
137 def identifier=(identifier)
138 super unless identifier_frozen?
138 super unless identifier_frozen?
139 end
139 end
140
140
141 def identifier_frozen?
141 def identifier_frozen?
142 errors[:identifier].blank? && !(new_record? || identifier.blank?)
142 errors[:identifier].blank? && !(new_record? || identifier.blank?)
143 end
143 end
144
144
145 # returns latest created projects
145 # returns latest created projects
146 # non public projects will be returned only if user is a member of those
146 # non public projects will be returned only if user is a member of those
147 def self.latest(user=nil, count=5)
147 def self.latest(user=nil, count=5)
148 visible(user).limit(count).order("created_on DESC").to_a
148 visible(user).limit(count).order("created_on DESC").to_a
149 end
149 end
150
150
151 # Returns true if the project is visible to +user+ or to the current user.
151 # Returns true if the project is visible to +user+ or to the current user.
152 def visible?(user=User.current)
152 def visible?(user=User.current)
153 user.allowed_to?(:view_project, self)
153 user.allowed_to?(:view_project, self)
154 end
154 end
155
155
156 # Returns a SQL conditions string used to find all projects visible by the specified user.
156 # Returns a SQL conditions string used to find all projects visible by the specified user.
157 #
157 #
158 # Examples:
158 # Examples:
159 # Project.visible_condition(admin) => "projects.status = 1"
159 # Project.visible_condition(admin) => "projects.status = 1"
160 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
160 # Project.visible_condition(normal_user) => "((projects.status = 1) AND (projects.is_public = 1 OR projects.id IN (1,3,4)))"
161 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
161 # Project.visible_condition(anonymous) => "((projects.status = 1) AND (projects.is_public = 1))"
162 def self.visible_condition(user, options={})
162 def self.visible_condition(user, options={})
163 allowed_to_condition(user, :view_project, options)
163 allowed_to_condition(user, :view_project, options)
164 end
164 end
165
165
166 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
166 # Returns a SQL conditions string used to find all projects for which +user+ has the given +permission+
167 #
167 #
168 # Valid options:
168 # Valid options:
169 # * :project => limit the condition to project
169 # * :project => limit the condition to project
170 # * :with_subprojects => limit the condition to project and its subprojects
170 # * :with_subprojects => limit the condition to project and its subprojects
171 # * :member => limit the condition to the user projects
171 # * :member => limit the condition to the user projects
172 def self.allowed_to_condition(user, permission, options={})
172 def self.allowed_to_condition(user, permission, options={})
173 perm = Redmine::AccessControl.permission(permission)
173 perm = Redmine::AccessControl.permission(permission)
174 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
174 base_statement = (perm && perm.read? ? "#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED}" : "#{Project.table_name}.status = #{Project::STATUS_ACTIVE}")
175 if perm && perm.project_module
175 if perm && perm.project_module
176 # If the permission belongs to a project module, make sure the module is enabled
176 # If the permission belongs to a project module, make sure the module is enabled
177 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
177 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
178 end
178 end
179 if project = options[:project]
179 if project = options[:project]
180 project_statement = project.project_condition(options[:with_subprojects])
180 project_statement = project.project_condition(options[:with_subprojects])
181 base_statement = "(#{project_statement}) AND (#{base_statement})"
181 base_statement = "(#{project_statement}) AND (#{base_statement})"
182 end
182 end
183
183
184 if user.admin?
184 if user.admin?
185 base_statement
185 base_statement
186 else
186 else
187 statement_by_role = {}
187 statement_by_role = {}
188 unless options[:member]
188 unless options[:member]
189 role = user.builtin_role
189 role = user.builtin_role
190 if role.allowed_to?(permission)
190 if role.allowed_to?(permission)
191 statement_by_role[role] = "#{Project.table_name}.is_public = #{connection.quoted_true}"
191 s = "#{Project.table_name}.is_public = #{connection.quoted_true}"
192 if user.id
193 s = "(#{s} AND #{Project.table_name}.id NOT IN (SELECT project_id FROM #{Member.table_name} WHERE user_id = #{user.id}))"
194 end
195 statement_by_role[role] = s
192 end
196 end
193 end
197 end
194 user.projects_by_role.each do |role, projects|
198 user.projects_by_role.each do |role, projects|
195 if role.allowed_to?(permission) && projects.any?
199 if role.allowed_to?(permission) && projects.any?
196 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
200 statement_by_role[role] = "#{Project.table_name}.id IN (#{projects.collect(&:id).join(',')})"
197 end
201 end
198 end
202 end
199 if statement_by_role.empty?
203 if statement_by_role.empty?
200 "1=0"
204 "1=0"
201 else
205 else
202 if block_given?
206 if block_given?
203 statement_by_role.each do |role, statement|
207 statement_by_role.each do |role, statement|
204 if s = yield(role, user)
208 if s = yield(role, user)
205 statement_by_role[role] = "(#{statement} AND (#{s}))"
209 statement_by_role[role] = "(#{statement} AND (#{s}))"
206 end
210 end
207 end
211 end
208 end
212 end
209 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
213 "((#{base_statement}) AND (#{statement_by_role.values.join(' OR ')}))"
210 end
214 end
211 end
215 end
212 end
216 end
213
217
214 def override_roles(role)
218 def override_roles(role)
215 @override_members ||= memberships.
219 @override_members ||= memberships.
216 joins(:principal).
220 joins(:principal).
217 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
221 where(:users => {:type => ['GroupAnonymous', 'GroupNonMember']}).to_a
218
222
219 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
223 group_class = role.anonymous? ? GroupAnonymous : GroupNonMember
220 member = @override_members.detect {|m| m.principal.is_a? group_class}
224 member = @override_members.detect {|m| m.principal.is_a? group_class}
221 member ? member.roles.to_a : [role]
225 member ? member.roles.to_a : [role]
222 end
226 end
223
227
224 def principals
228 def principals
225 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
229 @principals ||= Principal.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
226 end
230 end
227
231
228 def users
232 def users
229 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
233 @users ||= User.active.joins(:members).where("#{Member.table_name}.project_id = ?", id).uniq
230 end
234 end
231
235
232 # Returns the Systemwide and project specific activities
236 # Returns the Systemwide and project specific activities
233 def activities(include_inactive=false)
237 def activities(include_inactive=false)
234 t = TimeEntryActivity.table_name
238 t = TimeEntryActivity.table_name
235 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
239 scope = TimeEntryActivity.where("#{t}.project_id IS NULL OR #{t}.project_id = ?", id)
236
240
237 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
241 overridden_activity_ids = self.time_entry_activities.pluck(:parent_id).compact
238 if overridden_activity_ids.any?
242 if overridden_activity_ids.any?
239 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
243 scope = scope.where("#{t}.id NOT IN (?)", overridden_activity_ids)
240 end
244 end
241 unless include_inactive
245 unless include_inactive
242 scope = scope.active
246 scope = scope.active
243 end
247 end
244 scope
248 scope
245 end
249 end
246
250
247 # Will create a new Project specific Activity or update an existing one
251 # Will create a new Project specific Activity or update an existing one
248 #
252 #
249 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
253 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
250 # does not successfully save.
254 # does not successfully save.
251 def update_or_create_time_entry_activity(id, activity_hash)
255 def update_or_create_time_entry_activity(id, activity_hash)
252 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
256 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
253 self.create_time_entry_activity_if_needed(activity_hash)
257 self.create_time_entry_activity_if_needed(activity_hash)
254 else
258 else
255 activity = project.time_entry_activities.find_by_id(id.to_i)
259 activity = project.time_entry_activities.find_by_id(id.to_i)
256 activity.update_attributes(activity_hash) if activity
260 activity.update_attributes(activity_hash) if activity
257 end
261 end
258 end
262 end
259
263
260 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
264 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
261 #
265 #
262 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
266 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
263 # does not successfully save.
267 # does not successfully save.
264 def create_time_entry_activity_if_needed(activity)
268 def create_time_entry_activity_if_needed(activity)
265 if activity['parent_id']
269 if activity['parent_id']
266 parent_activity = TimeEntryActivity.find(activity['parent_id'])
270 parent_activity = TimeEntryActivity.find(activity['parent_id'])
267 activity['name'] = parent_activity.name
271 activity['name'] = parent_activity.name
268 activity['position'] = parent_activity.position
272 activity['position'] = parent_activity.position
269 if Enumeration.overriding_change?(activity, parent_activity)
273 if Enumeration.overriding_change?(activity, parent_activity)
270 project_activity = self.time_entry_activities.create(activity)
274 project_activity = self.time_entry_activities.create(activity)
271 if project_activity.new_record?
275 if project_activity.new_record?
272 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
276 raise ActiveRecord::Rollback, "Overriding TimeEntryActivity was not successfully saved"
273 else
277 else
274 self.time_entries.
278 self.time_entries.
275 where(["activity_id = ?", parent_activity.id]).
279 where(["activity_id = ?", parent_activity.id]).
276 update_all("activity_id = #{project_activity.id}")
280 update_all("activity_id = #{project_activity.id}")
277 end
281 end
278 end
282 end
279 end
283 end
280 end
284 end
281
285
282 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
286 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
283 #
287 #
284 # Examples:
288 # Examples:
285 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
289 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
286 # project.project_condition(false) => "projects.id = 1"
290 # project.project_condition(false) => "projects.id = 1"
287 def project_condition(with_subprojects)
291 def project_condition(with_subprojects)
288 cond = "#{Project.table_name}.id = #{id}"
292 cond = "#{Project.table_name}.id = #{id}"
289 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
293 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
290 cond
294 cond
291 end
295 end
292
296
293 def self.find(*args)
297 def self.find(*args)
294 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
298 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
295 project = find_by_identifier(*args)
299 project = find_by_identifier(*args)
296 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
300 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
297 project
301 project
298 else
302 else
299 super
303 super
300 end
304 end
301 end
305 end
302
306
303 def self.find_by_param(*args)
307 def self.find_by_param(*args)
304 self.find(*args)
308 self.find(*args)
305 end
309 end
306
310
307 alias :base_reload :reload
311 alias :base_reload :reload
308 def reload(*args)
312 def reload(*args)
309 @principals = nil
313 @principals = nil
310 @users = nil
314 @users = nil
311 @shared_versions = nil
315 @shared_versions = nil
312 @rolled_up_versions = nil
316 @rolled_up_versions = nil
313 @rolled_up_trackers = nil
317 @rolled_up_trackers = nil
314 @all_issue_custom_fields = nil
318 @all_issue_custom_fields = nil
315 @all_time_entry_custom_fields = nil
319 @all_time_entry_custom_fields = nil
316 @to_param = nil
320 @to_param = nil
317 @allowed_parents = nil
321 @allowed_parents = nil
318 @allowed_permissions = nil
322 @allowed_permissions = nil
319 @actions_allowed = nil
323 @actions_allowed = nil
320 @start_date = nil
324 @start_date = nil
321 @due_date = nil
325 @due_date = nil
322 @override_members = nil
326 @override_members = nil
323 @assignable_users = nil
327 @assignable_users = nil
324 base_reload(*args)
328 base_reload(*args)
325 end
329 end
326
330
327 def to_param
331 def to_param
328 # id is used for projects with a numeric identifier (compatibility)
332 # id is used for projects with a numeric identifier (compatibility)
329 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
333 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id.to_s : identifier)
330 end
334 end
331
335
332 def active?
336 def active?
333 self.status == STATUS_ACTIVE
337 self.status == STATUS_ACTIVE
334 end
338 end
335
339
336 def archived?
340 def archived?
337 self.status == STATUS_ARCHIVED
341 self.status == STATUS_ARCHIVED
338 end
342 end
339
343
340 # Archives the project and its descendants
344 # Archives the project and its descendants
341 def archive
345 def archive
342 # Check that there is no issue of a non descendant project that is assigned
346 # Check that there is no issue of a non descendant project that is assigned
343 # to one of the project or descendant versions
347 # to one of the project or descendant versions
344 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
348 version_ids = self_and_descendants.joins(:versions).pluck("#{Version.table_name}.id")
345
349
346 if version_ids.any? &&
350 if version_ids.any? &&
347 Issue.
351 Issue.
348 includes(:project).
352 includes(:project).
349 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
353 where("#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?", lft, rgt).
350 where(:fixed_version_id => version_ids).
354 where(:fixed_version_id => version_ids).
351 exists?
355 exists?
352 return false
356 return false
353 end
357 end
354 Project.transaction do
358 Project.transaction do
355 archive!
359 archive!
356 end
360 end
357 true
361 true
358 end
362 end
359
363
360 # Unarchives the project
364 # Unarchives the project
361 # All its ancestors must be active
365 # All its ancestors must be active
362 def unarchive
366 def unarchive
363 return false if ancestors.detect {|a| !a.active?}
367 return false if ancestors.detect {|a| !a.active?}
364 update_attribute :status, STATUS_ACTIVE
368 update_attribute :status, STATUS_ACTIVE
365 end
369 end
366
370
367 def close
371 def close
368 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
372 self_and_descendants.status(STATUS_ACTIVE).update_all :status => STATUS_CLOSED
369 end
373 end
370
374
371 def reopen
375 def reopen
372 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
376 self_and_descendants.status(STATUS_CLOSED).update_all :status => STATUS_ACTIVE
373 end
377 end
374
378
375 # Returns an array of projects the project can be moved to
379 # Returns an array of projects the project can be moved to
376 # by the current user
380 # by the current user
377 def allowed_parents(user=User.current)
381 def allowed_parents(user=User.current)
378 return @allowed_parents if @allowed_parents
382 return @allowed_parents if @allowed_parents
379 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
383 @allowed_parents = Project.allowed_to(user, :add_subprojects).to_a
380 @allowed_parents = @allowed_parents - self_and_descendants
384 @allowed_parents = @allowed_parents - self_and_descendants
381 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
385 if user.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
382 @allowed_parents << nil
386 @allowed_parents << nil
383 end
387 end
384 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
388 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
385 @allowed_parents << parent
389 @allowed_parents << parent
386 end
390 end
387 @allowed_parents
391 @allowed_parents
388 end
392 end
389
393
390 # Sets the parent of the project with authorization check
394 # Sets the parent of the project with authorization check
391 def set_allowed_parent!(p)
395 def set_allowed_parent!(p)
392 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
396 ActiveSupport::Deprecation.warn "Project#set_allowed_parent! is deprecated and will be removed in Redmine 4, use #safe_attributes= instead."
393 p = p.id if p.is_a?(Project)
397 p = p.id if p.is_a?(Project)
394 send :safe_attributes, {:project_id => p}
398 send :safe_attributes, {:project_id => p}
395 save
399 save
396 end
400 end
397
401
398 # Sets the parent of the project and saves the project
402 # Sets the parent of the project and saves the project
399 # Argument can be either a Project, a String, a Fixnum or nil
403 # Argument can be either a Project, a String, a Fixnum or nil
400 def set_parent!(p)
404 def set_parent!(p)
401 if p.is_a?(Project)
405 if p.is_a?(Project)
402 self.parent = p
406 self.parent = p
403 else
407 else
404 self.parent_id = p
408 self.parent_id = p
405 end
409 end
406 save
410 save
407 end
411 end
408
412
409 # Returns an array of the trackers used by the project and its active sub projects
413 # Returns an array of the trackers used by the project and its active sub projects
410 def rolled_up_trackers
414 def rolled_up_trackers
411 @rolled_up_trackers ||=
415 @rolled_up_trackers ||=
412 Tracker.
416 Tracker.
413 joins(:projects).
417 joins(:projects).
414 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
418 joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'").
415 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED).
419 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED).
416 uniq.
420 uniq.
417 sorted.
421 sorted.
418 to_a
422 to_a
419 end
423 end
420
424
421 # Closes open and locked project versions that are completed
425 # Closes open and locked project versions that are completed
422 def close_completed_versions
426 def close_completed_versions
423 Version.transaction do
427 Version.transaction do
424 versions.where(:status => %w(open locked)).each do |version|
428 versions.where(:status => %w(open locked)).each do |version|
425 if version.completed?
429 if version.completed?
426 version.update_attribute(:status, 'closed')
430 version.update_attribute(:status, 'closed')
427 end
431 end
428 end
432 end
429 end
433 end
430 end
434 end
431
435
432 # Returns a scope of the Versions on subprojects
436 # Returns a scope of the Versions on subprojects
433 def rolled_up_versions
437 def rolled_up_versions
434 @rolled_up_versions ||=
438 @rolled_up_versions ||=
435 Version.
439 Version.
436 joins(:project).
440 joins(:project).
437 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
441 where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED)
438 end
442 end
439
443
440 # Returns a scope of the Versions used by the project
444 # Returns a scope of the Versions used by the project
441 def shared_versions
445 def shared_versions
442 if new_record?
446 if new_record?
443 Version.
447 Version.
444 joins(:project).
448 joins(:project).
445 preload(:project).
449 preload(:project).
446 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
450 where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED)
447 else
451 else
448 @shared_versions ||= begin
452 @shared_versions ||= begin
449 r = root? ? self : root
453 r = root? ? self : root
450 Version.
454 Version.
451 joins(:project).
455 joins(:project).
452 preload(:project).
456 preload(:project).
453 where("#{Project.table_name}.id = #{id}" +
457 where("#{Project.table_name}.id = #{id}" +
454 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
458 " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" +
455 " #{Version.table_name}.sharing = 'system'" +
459 " #{Version.table_name}.sharing = 'system'" +
456 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
460 " OR (#{Project.table_name}.lft >= #{r.lft} AND #{Project.table_name}.rgt <= #{r.rgt} AND #{Version.table_name}.sharing = 'tree')" +
457 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
461 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
458 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
462 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
459 "))")
463 "))")
460 end
464 end
461 end
465 end
462 end
466 end
463
467
464 # Returns a hash of project users grouped by role
468 # Returns a hash of project users grouped by role
465 def users_by_role
469 def users_by_role
466 members.includes(:user, :roles).inject({}) do |h, m|
470 members.includes(:user, :roles).inject({}) do |h, m|
467 m.roles.each do |r|
471 m.roles.each do |r|
468 h[r] ||= []
472 h[r] ||= []
469 h[r] << m.user
473 h[r] << m.user
470 end
474 end
471 h
475 h
472 end
476 end
473 end
477 end
474
478
475 # Adds user as a project member with the default role
479 # Adds user as a project member with the default role
476 # Used for when a non-admin user creates a project
480 # Used for when a non-admin user creates a project
477 def add_default_member(user)
481 def add_default_member(user)
478 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
482 role = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
479 member = Member.new(:project => self, :principal => user, :roles => [role])
483 member = Member.new(:project => self, :principal => user, :roles => [role])
480 self.members << member
484 self.members << member
481 member
485 member
482 end
486 end
483
487
484 # Deletes all project's members
488 # Deletes all project's members
485 def delete_all_members
489 def delete_all_members
486 me, mr = Member.table_name, MemberRole.table_name
490 me, mr = Member.table_name, MemberRole.table_name
487 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
491 self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
488 Member.delete_all(['project_id = ?', id])
492 Member.delete_all(['project_id = ?', id])
489 end
493 end
490
494
491 # Return a Principal scope of users/groups issues can be assigned to
495 # Return a Principal scope of users/groups issues can be assigned to
492 def assignable_users
496 def assignable_users
493 types = ['User']
497 types = ['User']
494 types << 'Group' if Setting.issue_group_assignment?
498 types << 'Group' if Setting.issue_group_assignment?
495
499
496 @assignable_users ||= Principal.
500 @assignable_users ||= Principal.
497 active.
501 active.
498 joins(:members => :roles).
502 joins(:members => :roles).
499 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
503 where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}).
500 uniq.
504 uniq.
501 sorted
505 sorted
502 end
506 end
503
507
504 # Returns the mail addresses of users that should be always notified on project events
508 # Returns the mail addresses of users that should be always notified on project events
505 def recipients
509 def recipients
506 notified_users.collect {|user| user.mail}
510 notified_users.collect {|user| user.mail}
507 end
511 end
508
512
509 # Returns the users that should be notified on project events
513 # Returns the users that should be notified on project events
510 def notified_users
514 def notified_users
511 # TODO: User part should be extracted to User#notify_about?
515 # TODO: User part should be extracted to User#notify_about?
512 members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
516 members.select {|m| m.principal.present? && (m.mail_notification? || m.principal.mail_notification == 'all')}.collect {|m| m.principal}
513 end
517 end
514
518
515 # Returns a scope of all custom fields enabled for project issues
519 # Returns a scope of all custom fields enabled for project issues
516 # (explicitly associated custom fields and custom fields enabled for all projects)
520 # (explicitly associated custom fields and custom fields enabled for all projects)
517 def all_issue_custom_fields
521 def all_issue_custom_fields
518 @all_issue_custom_fields ||= IssueCustomField.
522 @all_issue_custom_fields ||= IssueCustomField.
519 sorted.
523 sorted.
520 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
524 where("is_for_all = ? OR id IN (SELECT DISTINCT cfp.custom_field_id" +
521 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
525 " FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} cfp" +
522 " WHERE cfp.project_id = ?)", true, id)
526 " WHERE cfp.project_id = ?)", true, id)
523 end
527 end
524
528
525 def project
529 def project
526 self
530 self
527 end
531 end
528
532
529 def <=>(project)
533 def <=>(project)
530 name.downcase <=> project.name.downcase
534 name.downcase <=> project.name.downcase
531 end
535 end
532
536
533 def to_s
537 def to_s
534 name
538 name
535 end
539 end
536
540
537 # Returns a short description of the projects (first lines)
541 # Returns a short description of the projects (first lines)
538 def short_description(length = 255)
542 def short_description(length = 255)
539 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
543 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
540 end
544 end
541
545
542 def css_classes
546 def css_classes
543 s = 'project'
547 s = 'project'
544 s << ' root' if root?
548 s << ' root' if root?
545 s << ' child' if child?
549 s << ' child' if child?
546 s << (leaf? ? ' leaf' : ' parent')
550 s << (leaf? ? ' leaf' : ' parent')
547 unless active?
551 unless active?
548 if archived?
552 if archived?
549 s << ' archived'
553 s << ' archived'
550 else
554 else
551 s << ' closed'
555 s << ' closed'
552 end
556 end
553 end
557 end
554 s
558 s
555 end
559 end
556
560
557 # The earliest start date of a project, based on it's issues and versions
561 # The earliest start date of a project, based on it's issues and versions
558 def start_date
562 def start_date
559 @start_date ||= [
563 @start_date ||= [
560 issues.minimum('start_date'),
564 issues.minimum('start_date'),
561 shared_versions.minimum('effective_date'),
565 shared_versions.minimum('effective_date'),
562 Issue.fixed_version(shared_versions).minimum('start_date')
566 Issue.fixed_version(shared_versions).minimum('start_date')
563 ].compact.min
567 ].compact.min
564 end
568 end
565
569
566 # The latest due date of an issue or version
570 # The latest due date of an issue or version
567 def due_date
571 def due_date
568 @due_date ||= [
572 @due_date ||= [
569 issues.maximum('due_date'),
573 issues.maximum('due_date'),
570 shared_versions.maximum('effective_date'),
574 shared_versions.maximum('effective_date'),
571 Issue.fixed_version(shared_versions).maximum('due_date')
575 Issue.fixed_version(shared_versions).maximum('due_date')
572 ].compact.max
576 ].compact.max
573 end
577 end
574
578
575 def overdue?
579 def overdue?
576 active? && !due_date.nil? && (due_date < Date.today)
580 active? && !due_date.nil? && (due_date < Date.today)
577 end
581 end
578
582
579 # Returns the percent completed for this project, based on the
583 # Returns the percent completed for this project, based on the
580 # progress on it's versions.
584 # progress on it's versions.
581 def completed_percent(options={:include_subprojects => false})
585 def completed_percent(options={:include_subprojects => false})
582 if options.delete(:include_subprojects)
586 if options.delete(:include_subprojects)
583 total = self_and_descendants.collect(&:completed_percent).sum
587 total = self_and_descendants.collect(&:completed_percent).sum
584
588
585 total / self_and_descendants.count
589 total / self_and_descendants.count
586 else
590 else
587 if versions.count > 0
591 if versions.count > 0
588 total = versions.collect(&:completed_percent).sum
592 total = versions.collect(&:completed_percent).sum
589
593
590 total / versions.count
594 total / versions.count
591 else
595 else
592 100
596 100
593 end
597 end
594 end
598 end
595 end
599 end
596
600
597 # Return true if this project allows to do the specified action.
601 # Return true if this project allows to do the specified action.
598 # action can be:
602 # action can be:
599 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
603 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
600 # * a permission Symbol (eg. :edit_project)
604 # * a permission Symbol (eg. :edit_project)
601 def allows_to?(action)
605 def allows_to?(action)
602 if archived?
606 if archived?
603 # No action allowed on archived projects
607 # No action allowed on archived projects
604 return false
608 return false
605 end
609 end
606 unless active? || Redmine::AccessControl.read_action?(action)
610 unless active? || Redmine::AccessControl.read_action?(action)
607 # No write action allowed on closed projects
611 # No write action allowed on closed projects
608 return false
612 return false
609 end
613 end
610 # No action allowed on disabled modules
614 # No action allowed on disabled modules
611 if action.is_a? Hash
615 if action.is_a? Hash
612 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
616 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
613 else
617 else
614 allowed_permissions.include? action
618 allowed_permissions.include? action
615 end
619 end
616 end
620 end
617
621
618 # Return the enabled module with the given name
622 # Return the enabled module with the given name
619 # or nil if the module is not enabled for the project
623 # or nil if the module is not enabled for the project
620 def enabled_module(name)
624 def enabled_module(name)
621 name = name.to_s
625 name = name.to_s
622 enabled_modules.detect {|m| m.name == name}
626 enabled_modules.detect {|m| m.name == name}
623 end
627 end
624
628
625 # Return true if the module with the given name is enabled
629 # Return true if the module with the given name is enabled
626 def module_enabled?(name)
630 def module_enabled?(name)
627 enabled_module(name).present?
631 enabled_module(name).present?
628 end
632 end
629
633
630 def enabled_module_names=(module_names)
634 def enabled_module_names=(module_names)
631 if module_names && module_names.is_a?(Array)
635 if module_names && module_names.is_a?(Array)
632 module_names = module_names.collect(&:to_s).reject(&:blank?)
636 module_names = module_names.collect(&:to_s).reject(&:blank?)
633 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
637 self.enabled_modules = module_names.collect {|name| enabled_modules.detect {|mod| mod.name == name} || EnabledModule.new(:name => name)}
634 else
638 else
635 enabled_modules.clear
639 enabled_modules.clear
636 end
640 end
637 end
641 end
638
642
639 # Returns an array of the enabled modules names
643 # Returns an array of the enabled modules names
640 def enabled_module_names
644 def enabled_module_names
641 enabled_modules.collect(&:name)
645 enabled_modules.collect(&:name)
642 end
646 end
643
647
644 # Enable a specific module
648 # Enable a specific module
645 #
649 #
646 # Examples:
650 # Examples:
647 # project.enable_module!(:issue_tracking)
651 # project.enable_module!(:issue_tracking)
648 # project.enable_module!("issue_tracking")
652 # project.enable_module!("issue_tracking")
649 def enable_module!(name)
653 def enable_module!(name)
650 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
654 enabled_modules << EnabledModule.new(:name => name.to_s) unless module_enabled?(name)
651 end
655 end
652
656
653 # Disable a module if it exists
657 # Disable a module if it exists
654 #
658 #
655 # Examples:
659 # Examples:
656 # project.disable_module!(:issue_tracking)
660 # project.disable_module!(:issue_tracking)
657 # project.disable_module!("issue_tracking")
661 # project.disable_module!("issue_tracking")
658 # project.disable_module!(project.enabled_modules.first)
662 # project.disable_module!(project.enabled_modules.first)
659 def disable_module!(target)
663 def disable_module!(target)
660 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
664 target = enabled_modules.detect{|mod| target.to_s == mod.name} unless enabled_modules.include?(target)
661 target.destroy unless target.blank?
665 target.destroy unless target.blank?
662 end
666 end
663
667
664 safe_attributes 'name',
668 safe_attributes 'name',
665 'description',
669 'description',
666 'homepage',
670 'homepage',
667 'is_public',
671 'is_public',
668 'identifier',
672 'identifier',
669 'custom_field_values',
673 'custom_field_values',
670 'custom_fields',
674 'custom_fields',
671 'tracker_ids',
675 'tracker_ids',
672 'issue_custom_field_ids',
676 'issue_custom_field_ids',
673 'parent_id'
677 'parent_id'
674
678
675 safe_attributes 'enabled_module_names',
679 safe_attributes 'enabled_module_names',
676 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
680 :if => lambda {|project, user| project.new_record? || user.allowed_to?(:select_project_modules, project) }
677
681
678 safe_attributes 'inherit_members',
682 safe_attributes 'inherit_members',
679 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
683 :if => lambda {|project, user| project.parent.nil? || project.parent.visible?(user)}
680
684
681 def safe_attributes=(attrs, user=User.current)
685 def safe_attributes=(attrs, user=User.current)
682 return unless attrs.is_a?(Hash)
686 return unless attrs.is_a?(Hash)
683 attrs = attrs.deep_dup
687 attrs = attrs.deep_dup
684
688
685 @unallowed_parent_id = nil
689 @unallowed_parent_id = nil
686 parent_id_param = attrs['parent_id'].to_s
690 parent_id_param = attrs['parent_id'].to_s
687 if parent_id_param.blank? || parent_id_param != parent_id.to_s
691 if parent_id_param.blank? || parent_id_param != parent_id.to_s
688 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
692 p = parent_id_param.present? ? Project.find_by_id(parent_id_param) : nil
689 unless allowed_parents(user).include?(p)
693 unless allowed_parents(user).include?(p)
690 attrs.delete('parent_id')
694 attrs.delete('parent_id')
691 @unallowed_parent_id = true
695 @unallowed_parent_id = true
692 end
696 end
693 end
697 end
694
698
695 super(attrs, user)
699 super(attrs, user)
696 end
700 end
697
701
698 # Returns an auto-generated project identifier based on the last identifier used
702 # Returns an auto-generated project identifier based on the last identifier used
699 def self.next_identifier
703 def self.next_identifier
700 p = Project.order('id DESC').first
704 p = Project.order('id DESC').first
701 p.nil? ? nil : p.identifier.to_s.succ
705 p.nil? ? nil : p.identifier.to_s.succ
702 end
706 end
703
707
704 # Copies and saves the Project instance based on the +project+.
708 # Copies and saves the Project instance based on the +project+.
705 # Duplicates the source project's:
709 # Duplicates the source project's:
706 # * Wiki
710 # * Wiki
707 # * Versions
711 # * Versions
708 # * Categories
712 # * Categories
709 # * Issues
713 # * Issues
710 # * Members
714 # * Members
711 # * Queries
715 # * Queries
712 #
716 #
713 # Accepts an +options+ argument to specify what to copy
717 # Accepts an +options+ argument to specify what to copy
714 #
718 #
715 # Examples:
719 # Examples:
716 # project.copy(1) # => copies everything
720 # project.copy(1) # => copies everything
717 # project.copy(1, :only => 'members') # => copies members only
721 # project.copy(1, :only => 'members') # => copies members only
718 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
722 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
719 def copy(project, options={})
723 def copy(project, options={})
720 project = project.is_a?(Project) ? project : Project.find(project)
724 project = project.is_a?(Project) ? project : Project.find(project)
721
725
722 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
726 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
723 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
727 to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
724
728
725 Project.transaction do
729 Project.transaction do
726 if save
730 if save
727 reload
731 reload
728 to_be_copied.each do |name|
732 to_be_copied.each do |name|
729 send "copy_#{name}", project
733 send "copy_#{name}", project
730 end
734 end
731 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
735 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
732 save
736 save
733 else
737 else
734 false
738 false
735 end
739 end
736 end
740 end
737 end
741 end
738
742
739 def member_principals
743 def member_principals
740 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
744 ActiveSupport::Deprecation.warn "Project#member_principals is deprecated and will be removed in Redmine 4.0. Use #memberships.active instead."
741 memberships.active
745 memberships.active
742 end
746 end
743
747
744 # Returns a new unsaved Project instance with attributes copied from +project+
748 # Returns a new unsaved Project instance with attributes copied from +project+
745 def self.copy_from(project)
749 def self.copy_from(project)
746 project = project.is_a?(Project) ? project : Project.find(project)
750 project = project.is_a?(Project) ? project : Project.find(project)
747 # clear unique attributes
751 # clear unique attributes
748 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
752 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
749 copy = Project.new(attributes)
753 copy = Project.new(attributes)
750 copy.enabled_modules = project.enabled_modules
754 copy.enabled_modules = project.enabled_modules
751 copy.trackers = project.trackers
755 copy.trackers = project.trackers
752 copy.custom_values = project.custom_values.collect {|v| v.clone}
756 copy.custom_values = project.custom_values.collect {|v| v.clone}
753 copy.issue_custom_fields = project.issue_custom_fields
757 copy.issue_custom_fields = project.issue_custom_fields
754 copy
758 copy
755 end
759 end
756
760
757 # Yields the given block for each project with its level in the tree
761 # Yields the given block for each project with its level in the tree
758 def self.project_tree(projects, &block)
762 def self.project_tree(projects, &block)
759 ancestors = []
763 ancestors = []
760 projects.sort_by(&:lft).each do |project|
764 projects.sort_by(&:lft).each do |project|
761 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
765 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
762 ancestors.pop
766 ancestors.pop
763 end
767 end
764 yield project, ancestors.size
768 yield project, ancestors.size
765 ancestors << project
769 ancestors << project
766 end
770 end
767 end
771 end
768
772
769 private
773 private
770
774
771 def update_inherited_members
775 def update_inherited_members
772 if parent
776 if parent
773 if inherit_members? && !inherit_members_was
777 if inherit_members? && !inherit_members_was
774 remove_inherited_member_roles
778 remove_inherited_member_roles
775 add_inherited_member_roles
779 add_inherited_member_roles
776 elsif !inherit_members? && inherit_members_was
780 elsif !inherit_members? && inherit_members_was
777 remove_inherited_member_roles
781 remove_inherited_member_roles
778 end
782 end
779 end
783 end
780 end
784 end
781
785
782 def remove_inherited_member_roles
786 def remove_inherited_member_roles
783 member_roles = memberships.map(&:member_roles).flatten
787 member_roles = memberships.map(&:member_roles).flatten
784 member_role_ids = member_roles.map(&:id)
788 member_role_ids = member_roles.map(&:id)
785 member_roles.each do |member_role|
789 member_roles.each do |member_role|
786 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
790 if member_role.inherited_from && !member_role_ids.include?(member_role.inherited_from)
787 member_role.destroy
791 member_role.destroy
788 end
792 end
789 end
793 end
790 end
794 end
791
795
792 def add_inherited_member_roles
796 def add_inherited_member_roles
793 if inherit_members? && parent
797 if inherit_members? && parent
794 parent.memberships.each do |parent_member|
798 parent.memberships.each do |parent_member|
795 member = Member.find_or_new(self.id, parent_member.user_id)
799 member = Member.find_or_new(self.id, parent_member.user_id)
796 parent_member.member_roles.each do |parent_member_role|
800 parent_member.member_roles.each do |parent_member_role|
797 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
801 member.member_roles << MemberRole.new(:role => parent_member_role.role, :inherited_from => parent_member_role.id)
798 end
802 end
799 member.save!
803 member.save!
800 end
804 end
801 memberships.reset
805 memberships.reset
802 end
806 end
803 end
807 end
804
808
805 def update_versions_from_hierarchy_change
809 def update_versions_from_hierarchy_change
806 Issue.update_versions_from_hierarchy_change(self)
810 Issue.update_versions_from_hierarchy_change(self)
807 end
811 end
808
812
809 def validate_parent
813 def validate_parent
810 if @unallowed_parent_id
814 if @unallowed_parent_id
811 errors.add(:parent_id, :invalid)
815 errors.add(:parent_id, :invalid)
812 elsif parent_id_changed?
816 elsif parent_id_changed?
813 unless parent.nil? || (parent.active? && move_possible?(parent))
817 unless parent.nil? || (parent.active? && move_possible?(parent))
814 errors.add(:parent_id, :invalid)
818 errors.add(:parent_id, :invalid)
815 end
819 end
816 end
820 end
817 end
821 end
818
822
819 # Copies wiki from +project+
823 # Copies wiki from +project+
820 def copy_wiki(project)
824 def copy_wiki(project)
821 # Check that the source project has a wiki first
825 # Check that the source project has a wiki first
822 unless project.wiki.nil?
826 unless project.wiki.nil?
823 wiki = self.wiki || Wiki.new
827 wiki = self.wiki || Wiki.new
824 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
828 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
825 wiki_pages_map = {}
829 wiki_pages_map = {}
826 project.wiki.pages.each do |page|
830 project.wiki.pages.each do |page|
827 # Skip pages without content
831 # Skip pages without content
828 next if page.content.nil?
832 next if page.content.nil?
829 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
833 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
830 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
834 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
831 new_wiki_page.content = new_wiki_content
835 new_wiki_page.content = new_wiki_content
832 wiki.pages << new_wiki_page
836 wiki.pages << new_wiki_page
833 wiki_pages_map[page.id] = new_wiki_page
837 wiki_pages_map[page.id] = new_wiki_page
834 end
838 end
835
839
836 self.wiki = wiki
840 self.wiki = wiki
837 wiki.save
841 wiki.save
838 # Reproduce page hierarchy
842 # Reproduce page hierarchy
839 project.wiki.pages.each do |page|
843 project.wiki.pages.each do |page|
840 if page.parent_id && wiki_pages_map[page.id]
844 if page.parent_id && wiki_pages_map[page.id]
841 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
845 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
842 wiki_pages_map[page.id].save
846 wiki_pages_map[page.id].save
843 end
847 end
844 end
848 end
845 end
849 end
846 end
850 end
847
851
848 # Copies versions from +project+
852 # Copies versions from +project+
849 def copy_versions(project)
853 def copy_versions(project)
850 project.versions.each do |version|
854 project.versions.each do |version|
851 new_version = Version.new
855 new_version = Version.new
852 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
856 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
853 self.versions << new_version
857 self.versions << new_version
854 end
858 end
855 end
859 end
856
860
857 # Copies issue categories from +project+
861 # Copies issue categories from +project+
858 def copy_issue_categories(project)
862 def copy_issue_categories(project)
859 project.issue_categories.each do |issue_category|
863 project.issue_categories.each do |issue_category|
860 new_issue_category = IssueCategory.new
864 new_issue_category = IssueCategory.new
861 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
865 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
862 self.issue_categories << new_issue_category
866 self.issue_categories << new_issue_category
863 end
867 end
864 end
868 end
865
869
866 # Copies issues from +project+
870 # Copies issues from +project+
867 def copy_issues(project)
871 def copy_issues(project)
868 # Stores the source issue id as a key and the copied issues as the
872 # Stores the source issue id as a key and the copied issues as the
869 # value. Used to map the two together for issue relations.
873 # value. Used to map the two together for issue relations.
870 issues_map = {}
874 issues_map = {}
871
875
872 # Store status and reopen locked/closed versions
876 # Store status and reopen locked/closed versions
873 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
877 version_statuses = versions.reject(&:open?).map {|version| [version, version.status]}
874 version_statuses.each do |version, status|
878 version_statuses.each do |version, status|
875 version.update_attribute :status, 'open'
879 version.update_attribute :status, 'open'
876 end
880 end
877
881
878 # Get issues sorted by root_id, lft so that parent issues
882 # Get issues sorted by root_id, lft so that parent issues
879 # get copied before their children
883 # get copied before their children
880 project.issues.reorder('root_id, lft').each do |issue|
884 project.issues.reorder('root_id, lft').each do |issue|
881 new_issue = Issue.new
885 new_issue = Issue.new
882 new_issue.copy_from(issue, :subtasks => false, :link => false)
886 new_issue.copy_from(issue, :subtasks => false, :link => false)
883 new_issue.project = self
887 new_issue.project = self
884 # Changing project resets the custom field values
888 # Changing project resets the custom field values
885 # TODO: handle this in Issue#project=
889 # TODO: handle this in Issue#project=
886 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
890 new_issue.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
887 # Reassign fixed_versions by name, since names are unique per project
891 # Reassign fixed_versions by name, since names are unique per project
888 if issue.fixed_version && issue.fixed_version.project == project
892 if issue.fixed_version && issue.fixed_version.project == project
889 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
893 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
890 end
894 end
891 # Reassign the category by name, since names are unique per project
895 # Reassign the category by name, since names are unique per project
892 if issue.category
896 if issue.category
893 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
897 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
894 end
898 end
895 # Parent issue
899 # Parent issue
896 if issue.parent_id
900 if issue.parent_id
897 if copied_parent = issues_map[issue.parent_id]
901 if copied_parent = issues_map[issue.parent_id]
898 new_issue.parent_issue_id = copied_parent.id
902 new_issue.parent_issue_id = copied_parent.id
899 end
903 end
900 end
904 end
901
905
902 self.issues << new_issue
906 self.issues << new_issue
903 if new_issue.new_record?
907 if new_issue.new_record?
904 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
908 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info?
905 else
909 else
906 issues_map[issue.id] = new_issue unless new_issue.new_record?
910 issues_map[issue.id] = new_issue unless new_issue.new_record?
907 end
911 end
908 end
912 end
909
913
910 # Restore locked/closed version statuses
914 # Restore locked/closed version statuses
911 version_statuses.each do |version, status|
915 version_statuses.each do |version, status|
912 version.update_attribute :status, status
916 version.update_attribute :status, status
913 end
917 end
914
918
915 # Relations after in case issues related each other
919 # Relations after in case issues related each other
916 project.issues.each do |issue|
920 project.issues.each do |issue|
917 new_issue = issues_map[issue.id]
921 new_issue = issues_map[issue.id]
918 unless new_issue
922 unless new_issue
919 # Issue was not copied
923 # Issue was not copied
920 next
924 next
921 end
925 end
922
926
923 # Relations
927 # Relations
924 issue.relations_from.each do |source_relation|
928 issue.relations_from.each do |source_relation|
925 new_issue_relation = IssueRelation.new
929 new_issue_relation = IssueRelation.new
926 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
930 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
927 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
931 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
928 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
932 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
929 new_issue_relation.issue_to = source_relation.issue_to
933 new_issue_relation.issue_to = source_relation.issue_to
930 end
934 end
931 new_issue.relations_from << new_issue_relation
935 new_issue.relations_from << new_issue_relation
932 end
936 end
933
937
934 issue.relations_to.each do |source_relation|
938 issue.relations_to.each do |source_relation|
935 new_issue_relation = IssueRelation.new
939 new_issue_relation = IssueRelation.new
936 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
940 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
937 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
941 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
938 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
942 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
939 new_issue_relation.issue_from = source_relation.issue_from
943 new_issue_relation.issue_from = source_relation.issue_from
940 end
944 end
941 new_issue.relations_to << new_issue_relation
945 new_issue.relations_to << new_issue_relation
942 end
946 end
943 end
947 end
944 end
948 end
945
949
946 # Copies members from +project+
950 # Copies members from +project+
947 def copy_members(project)
951 def copy_members(project)
948 # Copy users first, then groups to handle members with inherited and given roles
952 # Copy users first, then groups to handle members with inherited and given roles
949 members_to_copy = []
953 members_to_copy = []
950 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
954 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
951 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
955 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
952
956
953 members_to_copy.each do |member|
957 members_to_copy.each do |member|
954 new_member = Member.new
958 new_member = Member.new
955 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
959 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
956 # only copy non inherited roles
960 # only copy non inherited roles
957 # inherited roles will be added when copying the group membership
961 # inherited roles will be added when copying the group membership
958 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
962 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
959 next if role_ids.empty?
963 next if role_ids.empty?
960 new_member.role_ids = role_ids
964 new_member.role_ids = role_ids
961 new_member.project = self
965 new_member.project = self
962 self.members << new_member
966 self.members << new_member
963 end
967 end
964 end
968 end
965
969
966 # Copies queries from +project+
970 # Copies queries from +project+
967 def copy_queries(project)
971 def copy_queries(project)
968 project.queries.each do |query|
972 project.queries.each do |query|
969 new_query = IssueQuery.new
973 new_query = IssueQuery.new
970 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
974 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type")
971 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
975 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
972 new_query.project = self
976 new_query.project = self
973 new_query.user_id = query.user_id
977 new_query.user_id = query.user_id
974 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
978 new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES
975 self.queries << new_query
979 self.queries << new_query
976 end
980 end
977 end
981 end
978
982
979 # Copies boards from +project+
983 # Copies boards from +project+
980 def copy_boards(project)
984 def copy_boards(project)
981 project.boards.each do |board|
985 project.boards.each do |board|
982 new_board = Board.new
986 new_board = Board.new
983 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
987 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
984 new_board.project = self
988 new_board.project = self
985 self.boards << new_board
989 self.boards << new_board
986 end
990 end
987 end
991 end
988
992
989 def allowed_permissions
993 def allowed_permissions
990 @allowed_permissions ||= begin
994 @allowed_permissions ||= begin
991 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
995 module_names = enabled_modules.loaded? ? enabled_modules.map(&:name) : enabled_modules.pluck(:name)
992 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
996 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
993 end
997 end
994 end
998 end
995
999
996 def allowed_actions
1000 def allowed_actions
997 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
1001 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
998 end
1002 end
999
1003
1000 # Archives subprojects recursively
1004 # Archives subprojects recursively
1001 def archive!
1005 def archive!
1002 children.each do |subproject|
1006 children.each do |subproject|
1003 subproject.send :archive!
1007 subproject.send :archive!
1004 end
1008 end
1005 update_attribute :status, STATUS_ARCHIVED
1009 update_attribute :status, STATUS_ARCHIVED
1006 end
1010 end
1007 end
1011 end
@@ -1,2704 +1,2713
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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, :email_addresses, :members, :member_roles, :roles,
21 fixtures :projects, :users, :email_addresses, :members, :member_roles, :roles,
22 :groups_users,
22 :groups_users,
23 :trackers, :projects_trackers,
23 :trackers, :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :versions,
25 :versions,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 :enumerations,
27 :enumerations,
28 :issues, :journals, :journal_details,
28 :issues, :journals, :journal_details,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 :time_entries
30 :time_entries
31
31
32 include Redmine::I18n
32 include Redmine::I18n
33
33
34 def setup
34 def setup
35 set_language_if_valid 'en'
35 set_language_if_valid 'en'
36 end
36 end
37
37
38 def teardown
38 def teardown
39 User.current = nil
39 User.current = nil
40 end
40 end
41
41
42 def test_initialize
42 def test_initialize
43 issue = Issue.new
43 issue = Issue.new
44
44
45 assert_nil issue.project_id
45 assert_nil issue.project_id
46 assert_nil issue.tracker_id
46 assert_nil issue.tracker_id
47 assert_nil issue.status_id
47 assert_nil issue.status_id
48 assert_nil issue.author_id
48 assert_nil issue.author_id
49 assert_nil issue.assigned_to_id
49 assert_nil issue.assigned_to_id
50 assert_nil issue.category_id
50 assert_nil issue.category_id
51
51
52 assert_equal IssuePriority.default, issue.priority
52 assert_equal IssuePriority.default, issue.priority
53 end
53 end
54
54
55 def test_create
55 def test_create
56 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
56 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
57 :status_id => 1, :priority => IssuePriority.all.first,
57 :status_id => 1, :priority => IssuePriority.all.first,
58 :subject => 'test_create',
58 :subject => 'test_create',
59 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
59 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
60 assert issue.save
60 assert issue.save
61 issue.reload
61 issue.reload
62 assert_equal 1.5, issue.estimated_hours
62 assert_equal 1.5, issue.estimated_hours
63 end
63 end
64
64
65 def test_create_minimal
65 def test_create_minimal
66 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create')
66 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create')
67 assert issue.save
67 assert issue.save
68 assert_equal issue.tracker.default_status, issue.status
68 assert_equal issue.tracker.default_status, issue.status
69 assert issue.description.nil?
69 assert issue.description.nil?
70 assert_nil issue.estimated_hours
70 assert_nil issue.estimated_hours
71 end
71 end
72
72
73 def test_create_with_all_fields_disabled
73 def test_create_with_all_fields_disabled
74 tracker = Tracker.find(1)
74 tracker = Tracker.find(1)
75 tracker.core_fields = []
75 tracker.core_fields = []
76 tracker.save!
76 tracker.save!
77
77
78 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create_with_all_fields_disabled')
78 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :subject => 'test_create_with_all_fields_disabled')
79 assert_save issue
79 assert_save issue
80 end
80 end
81
81
82 def test_start_date_format_should_be_validated
82 def test_start_date_format_should_be_validated
83 set_language_if_valid 'en'
83 set_language_if_valid 'en'
84 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
84 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
85 issue = Issue.new(:start_date => invalid_date)
85 issue = Issue.new(:start_date => invalid_date)
86 assert !issue.valid?
86 assert !issue.valid?
87 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
87 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
88 end
88 end
89 end
89 end
90
90
91 def test_due_date_format_should_be_validated
91 def test_due_date_format_should_be_validated
92 set_language_if_valid 'en'
92 set_language_if_valid 'en'
93 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
93 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
94 issue = Issue.new(:due_date => invalid_date)
94 issue = Issue.new(:due_date => invalid_date)
95 assert !issue.valid?
95 assert !issue.valid?
96 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
96 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
97 end
97 end
98 end
98 end
99
99
100 def test_due_date_lesser_than_start_date_should_not_validate
100 def test_due_date_lesser_than_start_date_should_not_validate
101 set_language_if_valid 'en'
101 set_language_if_valid 'en'
102 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
102 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
103 assert !issue.valid?
103 assert !issue.valid?
104 assert_include 'Due date must be greater than start date', issue.errors.full_messages
104 assert_include 'Due date must be greater than start date', issue.errors.full_messages
105 end
105 end
106
106
107 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
107 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
108 issue = Issue.generate(:start_date => '2013-06-04')
108 issue = Issue.generate(:start_date => '2013-06-04')
109 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
109 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
110 assert !issue.valid?
110 assert !issue.valid?
111 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
111 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
112 end
112 end
113
113
114 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
114 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
115 issue = Issue.generate!(:start_date => '2013-06-04')
115 issue = Issue.generate!(:start_date => '2013-06-04')
116 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
116 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
117 issue.start_date = '2013-06-07'
117 issue.start_date = '2013-06-07'
118 assert !issue.valid?
118 assert !issue.valid?
119 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
119 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
120 end
120 end
121
121
122 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
122 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
123 issue = Issue.generate!(:start_date => '2013-06-04')
123 issue = Issue.generate!(:start_date => '2013-06-04')
124 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
124 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
125 assert issue.valid?
125 assert issue.valid?
126 end
126 end
127
127
128 def test_estimated_hours_should_be_validated
128 def test_estimated_hours_should_be_validated
129 set_language_if_valid 'en'
129 set_language_if_valid 'en'
130 ['-2'].each do |invalid|
130 ['-2'].each do |invalid|
131 issue = Issue.new(:estimated_hours => invalid)
131 issue = Issue.new(:estimated_hours => invalid)
132 assert !issue.valid?
132 assert !issue.valid?
133 assert_include 'Estimated time is invalid', issue.errors.full_messages
133 assert_include 'Estimated time is invalid', issue.errors.full_messages
134 end
134 end
135 end
135 end
136
136
137 def test_create_with_required_custom_field
137 def test_create_with_required_custom_field
138 set_language_if_valid 'en'
138 set_language_if_valid 'en'
139 field = IssueCustomField.find_by_name('Database')
139 field = IssueCustomField.find_by_name('Database')
140 field.update_attribute(:is_required, true)
140 field.update_attribute(:is_required, true)
141
141
142 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
142 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
143 :status_id => 1, :subject => 'test_create',
143 :status_id => 1, :subject => 'test_create',
144 :description => 'IssueTest#test_create_with_required_custom_field')
144 :description => 'IssueTest#test_create_with_required_custom_field')
145 assert issue.available_custom_fields.include?(field)
145 assert issue.available_custom_fields.include?(field)
146 # No value for the custom field
146 # No value for the custom field
147 assert !issue.save
147 assert !issue.save
148 assert_equal ["Database cannot be blank"], issue.errors.full_messages
148 assert_equal ["Database cannot be blank"], issue.errors.full_messages
149 # Blank value
149 # Blank value
150 issue.custom_field_values = { field.id => '' }
150 issue.custom_field_values = { field.id => '' }
151 assert !issue.save
151 assert !issue.save
152 assert_equal ["Database cannot be blank"], issue.errors.full_messages
152 assert_equal ["Database cannot be blank"], issue.errors.full_messages
153 # Invalid value
153 # Invalid value
154 issue.custom_field_values = { field.id => 'SQLServer' }
154 issue.custom_field_values = { field.id => 'SQLServer' }
155 assert !issue.save
155 assert !issue.save
156 assert_equal ["Database is not included in the list"], issue.errors.full_messages
156 assert_equal ["Database is not included in the list"], issue.errors.full_messages
157 # Valid value
157 # Valid value
158 issue.custom_field_values = { field.id => 'PostgreSQL' }
158 issue.custom_field_values = { field.id => 'PostgreSQL' }
159 assert issue.save
159 assert issue.save
160 issue.reload
160 issue.reload
161 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
161 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
162 end
162 end
163
163
164 def test_create_with_group_assignment
164 def test_create_with_group_assignment
165 with_settings :issue_group_assignment => '1' do
165 with_settings :issue_group_assignment => '1' do
166 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
166 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
167 :subject => 'Group assignment',
167 :subject => 'Group assignment',
168 :assigned_to_id => 11).save
168 :assigned_to_id => 11).save
169 issue = Issue.order('id DESC').first
169 issue = Issue.order('id DESC').first
170 assert_kind_of Group, issue.assigned_to
170 assert_kind_of Group, issue.assigned_to
171 assert_equal Group.find(11), issue.assigned_to
171 assert_equal Group.find(11), issue.assigned_to
172 end
172 end
173 end
173 end
174
174
175 def test_create_with_parent_issue_id
175 def test_create_with_parent_issue_id
176 issue = Issue.new(:project_id => 1, :tracker_id => 1,
176 issue = Issue.new(:project_id => 1, :tracker_id => 1,
177 :author_id => 1, :subject => 'Group assignment',
177 :author_id => 1, :subject => 'Group assignment',
178 :parent_issue_id => 1)
178 :parent_issue_id => 1)
179 assert_save issue
179 assert_save issue
180 assert_equal 1, issue.parent_issue_id
180 assert_equal 1, issue.parent_issue_id
181 assert_equal Issue.find(1), issue.parent
181 assert_equal Issue.find(1), issue.parent
182 end
182 end
183
183
184 def test_create_with_sharp_parent_issue_id
184 def test_create_with_sharp_parent_issue_id
185 issue = Issue.new(:project_id => 1, :tracker_id => 1,
185 issue = Issue.new(:project_id => 1, :tracker_id => 1,
186 :author_id => 1, :subject => 'Group assignment',
186 :author_id => 1, :subject => 'Group assignment',
187 :parent_issue_id => "#1")
187 :parent_issue_id => "#1")
188 assert_save issue
188 assert_save issue
189 assert_equal 1, issue.parent_issue_id
189 assert_equal 1, issue.parent_issue_id
190 assert_equal Issue.find(1), issue.parent
190 assert_equal Issue.find(1), issue.parent
191 end
191 end
192
192
193 def test_create_with_invalid_parent_issue_id
193 def test_create_with_invalid_parent_issue_id
194 set_language_if_valid 'en'
194 set_language_if_valid 'en'
195 issue = Issue.new(:project_id => 1, :tracker_id => 1,
195 issue = Issue.new(:project_id => 1, :tracker_id => 1,
196 :author_id => 1, :subject => 'Group assignment',
196 :author_id => 1, :subject => 'Group assignment',
197 :parent_issue_id => '01ABC')
197 :parent_issue_id => '01ABC')
198 assert !issue.save
198 assert !issue.save
199 assert_equal '01ABC', issue.parent_issue_id
199 assert_equal '01ABC', issue.parent_issue_id
200 assert_include 'Parent task is invalid', issue.errors.full_messages
200 assert_include 'Parent task is invalid', issue.errors.full_messages
201 end
201 end
202
202
203 def test_create_with_invalid_sharp_parent_issue_id
203 def test_create_with_invalid_sharp_parent_issue_id
204 set_language_if_valid 'en'
204 set_language_if_valid 'en'
205 issue = Issue.new(:project_id => 1, :tracker_id => 1,
205 issue = Issue.new(:project_id => 1, :tracker_id => 1,
206 :author_id => 1, :subject => 'Group assignment',
206 :author_id => 1, :subject => 'Group assignment',
207 :parent_issue_id => '#01ABC')
207 :parent_issue_id => '#01ABC')
208 assert !issue.save
208 assert !issue.save
209 assert_equal '#01ABC', issue.parent_issue_id
209 assert_equal '#01ABC', issue.parent_issue_id
210 assert_include 'Parent task is invalid', issue.errors.full_messages
210 assert_include 'Parent task is invalid', issue.errors.full_messages
211 end
211 end
212
212
213 def assert_visibility_match(user, issues)
213 def assert_visibility_match(user, issues)
214 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
214 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
215 end
215 end
216
216
217 def test_visible_scope_for_anonymous
217 def test_visible_scope_for_anonymous
218 # Anonymous user should see issues of public projects only
218 # Anonymous user should see issues of public projects only
219 issues = Issue.visible(User.anonymous).to_a
219 issues = Issue.visible(User.anonymous).to_a
220 assert issues.any?
220 assert issues.any?
221 assert_nil issues.detect {|issue| !issue.project.is_public?}
221 assert_nil issues.detect {|issue| !issue.project.is_public?}
222 assert_nil issues.detect {|issue| issue.is_private?}
222 assert_nil issues.detect {|issue| issue.is_private?}
223 assert_visibility_match User.anonymous, issues
223 assert_visibility_match User.anonymous, issues
224 end
224 end
225
225
226 def test_visible_scope_for_anonymous_without_view_issues_permissions
226 def test_visible_scope_for_anonymous_without_view_issues_permissions
227 # Anonymous user should not see issues without permission
227 # Anonymous user should not see issues without permission
228 Role.anonymous.remove_permission!(:view_issues)
228 Role.anonymous.remove_permission!(:view_issues)
229 issues = Issue.visible(User.anonymous).to_a
229 issues = Issue.visible(User.anonymous).to_a
230 assert issues.empty?
230 assert issues.empty?
231 assert_visibility_match User.anonymous, issues
231 assert_visibility_match User.anonymous, issues
232 end
232 end
233
233
234 def test_visible_scope_for_anonymous_without_view_issues_permissions_and_membership
234 def test_visible_scope_for_anonymous_without_view_issues_permissions_and_membership
235 Role.anonymous.remove_permission!(:view_issues)
235 Role.anonymous.remove_permission!(:view_issues)
236 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
236 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [2])
237
237
238 issues = Issue.visible(User.anonymous).all
238 issues = Issue.visible(User.anonymous).all
239 assert issues.any?
239 assert issues.any?
240 assert_equal [1], issues.map(&:project_id).uniq.sort
240 assert_equal [1], issues.map(&:project_id).uniq.sort
241 assert_visibility_match User.anonymous, issues
241 assert_visibility_match User.anonymous, issues
242 end
242 end
243
243
244 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
244 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
245 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
245 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
246 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
246 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
247 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
247 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
248 assert !issue.visible?(User.anonymous)
248 assert !issue.visible?(User.anonymous)
249 end
249 end
250
250
251 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
251 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
252 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
252 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
253 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
253 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
254 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
254 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
255 assert !issue.visible?(User.anonymous)
255 assert !issue.visible?(User.anonymous)
256 end
256 end
257
257
258 def test_visible_scope_for_non_member
258 def test_visible_scope_for_non_member
259 user = User.find(9)
259 user = User.find(9)
260 assert user.projects.empty?
260 assert user.projects.empty?
261 # Non member user should see issues of public projects only
261 # Non member user should see issues of public projects only
262 issues = Issue.visible(user).to_a
262 issues = Issue.visible(user).to_a
263 assert issues.any?
263 assert issues.any?
264 assert_nil issues.detect {|issue| !issue.project.is_public?}
264 assert_nil issues.detect {|issue| !issue.project.is_public?}
265 assert_nil issues.detect {|issue| issue.is_private?}
265 assert_nil issues.detect {|issue| issue.is_private?}
266 assert_visibility_match user, issues
266 assert_visibility_match user, issues
267 end
267 end
268
268
269 def test_visible_scope_for_non_member_with_own_issues_visibility
269 def test_visible_scope_for_non_member_with_own_issues_visibility
270 Role.non_member.update_attribute :issues_visibility, 'own'
270 Role.non_member.update_attribute :issues_visibility, 'own'
271 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
271 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
272 user = User.find(9)
272 user = User.find(9)
273
273
274 issues = Issue.visible(user).to_a
274 issues = Issue.visible(user).to_a
275 assert issues.any?
275 assert issues.any?
276 assert_nil issues.detect {|issue| issue.author != user}
276 assert_nil issues.detect {|issue| issue.author != user}
277 assert_visibility_match user, issues
277 assert_visibility_match user, issues
278 end
278 end
279
279
280 def test_visible_scope_for_non_member_without_view_issues_permissions
280 def test_visible_scope_for_non_member_without_view_issues_permissions
281 # Non member user should not see issues without permission
281 # Non member user should not see issues without permission
282 Role.non_member.remove_permission!(:view_issues)
282 Role.non_member.remove_permission!(:view_issues)
283 user = User.find(9)
283 user = User.find(9)
284 assert user.projects.empty?
284 assert user.projects.empty?
285 issues = Issue.visible(user).to_a
285 issues = Issue.visible(user).to_a
286 assert issues.empty?
286 assert issues.empty?
287 assert_visibility_match user, issues
287 assert_visibility_match user, issues
288 end
288 end
289
289
290 def test_visible_scope_for_non_member_without_view_issues_permissions_and_membership
290 def test_visible_scope_for_non_member_without_view_issues_permissions_and_membership
291 Role.non_member.remove_permission!(:view_issues)
291 Role.non_member.remove_permission!(:view_issues)
292 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
292 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [2])
293 user = User.find(9)
293 user = User.find(9)
294
294
295 issues = Issue.visible(user).all
295 issues = Issue.visible(user).all
296 assert issues.any?
296 assert issues.any?
297 assert_equal [1], issues.map(&:project_id).uniq.sort
297 assert_equal [1], issues.map(&:project_id).uniq.sort
298 assert_visibility_match user, issues
298 assert_visibility_match user, issues
299 end
299 end
300
300
301 def test_visible_scope_for_member
301 def test_visible_scope_for_member
302 user = User.find(9)
302 user = User.find(9)
303 # User should see issues of projects for which user has view_issues permissions only
303 # User should see issues of projects for which user has view_issues permissions only
304 Role.non_member.remove_permission!(:view_issues)
304 Role.non_member.remove_permission!(:view_issues)
305 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
305 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
306 issues = Issue.visible(user).to_a
306 issues = Issue.visible(user).to_a
307 assert issues.any?
307 assert issues.any?
308 assert_nil issues.detect {|issue| issue.project_id != 3}
308 assert_nil issues.detect {|issue| issue.project_id != 3}
309 assert_nil issues.detect {|issue| issue.is_private?}
309 assert_nil issues.detect {|issue| issue.is_private?}
310 assert_visibility_match user, issues
310 assert_visibility_match user, issues
311 end
311 end
312
312
313 def test_visible_scope_for_member_without_view_issues_permission_and_non_member_role_having_the_permission
314 Role.non_member.add_permission!(:view_issues)
315 Role.find(1).remove_permission!(:view_issues)
316 user = User.find(2)
317
318 assert_equal 0, Issue.where(:project_id => 1).visible(user).count
319 assert_equal false, Issue.where(:project_id => 1).first.visible?(user)
320 end
321
313 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
322 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
314 user = User.find(8)
323 user = User.find(8)
315 assert user.groups.any?
324 assert user.groups.any?
316 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
325 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
317 Role.non_member.remove_permission!(:view_issues)
326 Role.non_member.remove_permission!(:view_issues)
318
327
319 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 3,
328 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 3,
320 :status_id => 1, :priority => IssuePriority.all.first,
329 :status_id => 1, :priority => IssuePriority.all.first,
321 :subject => 'Assignment test',
330 :subject => 'Assignment test',
322 :assigned_to => user.groups.first,
331 :assigned_to => user.groups.first,
323 :is_private => true)
332 :is_private => true)
324
333
325 Role.find(2).update_attribute :issues_visibility, 'default'
334 Role.find(2).update_attribute :issues_visibility, 'default'
326 issues = Issue.visible(User.find(8)).to_a
335 issues = Issue.visible(User.find(8)).to_a
327 assert issues.any?
336 assert issues.any?
328 assert issues.include?(issue)
337 assert issues.include?(issue)
329
338
330 Role.find(2).update_attribute :issues_visibility, 'own'
339 Role.find(2).update_attribute :issues_visibility, 'own'
331 issues = Issue.visible(User.find(8)).to_a
340 issues = Issue.visible(User.find(8)).to_a
332 assert issues.any?
341 assert issues.any?
333 assert_include issue, issues
342 assert_include issue, issues
334 end
343 end
335
344
336 def test_visible_scope_for_admin
345 def test_visible_scope_for_admin
337 user = User.find(1)
346 user = User.find(1)
338 user.members.each(&:destroy)
347 user.members.each(&:destroy)
339 assert user.projects.empty?
348 assert user.projects.empty?
340 issues = Issue.visible(user).to_a
349 issues = Issue.visible(user).to_a
341 assert issues.any?
350 assert issues.any?
342 # Admin should see issues on private projects that admin does not belong to
351 # Admin should see issues on private projects that admin does not belong to
343 assert issues.detect {|issue| !issue.project.is_public?}
352 assert issues.detect {|issue| !issue.project.is_public?}
344 # Admin should see private issues of other users
353 # Admin should see private issues of other users
345 assert issues.detect {|issue| issue.is_private? && issue.author != user}
354 assert issues.detect {|issue| issue.is_private? && issue.author != user}
346 assert_visibility_match user, issues
355 assert_visibility_match user, issues
347 end
356 end
348
357
349 def test_visible_scope_with_project
358 def test_visible_scope_with_project
350 project = Project.find(1)
359 project = Project.find(1)
351 issues = Issue.visible(User.find(2), :project => project).to_a
360 issues = Issue.visible(User.find(2), :project => project).to_a
352 projects = issues.collect(&:project).uniq
361 projects = issues.collect(&:project).uniq
353 assert_equal 1, projects.size
362 assert_equal 1, projects.size
354 assert_equal project, projects.first
363 assert_equal project, projects.first
355 end
364 end
356
365
357 def test_visible_scope_with_project_and_subprojects
366 def test_visible_scope_with_project_and_subprojects
358 project = Project.find(1)
367 project = Project.find(1)
359 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).to_a
368 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).to_a
360 projects = issues.collect(&:project).uniq
369 projects = issues.collect(&:project).uniq
361 assert projects.size > 1
370 assert projects.size > 1
362 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
371 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
363 end
372 end
364
373
365 def test_visible_and_nested_set_scopes
374 def test_visible_and_nested_set_scopes
366 user = User.generate!
375 user = User.generate!
367 parent = Issue.generate!(:assigned_to => user)
376 parent = Issue.generate!(:assigned_to => user)
368 assert parent.visible?(user)
377 assert parent.visible?(user)
369 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
378 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
370 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
379 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
371 parent.reload
380 parent.reload
372 child1.reload
381 child1.reload
373 child2.reload
382 child2.reload
374 assert child1.visible?(user)
383 assert child1.visible?(user)
375 assert child2.visible?(user)
384 assert child2.visible?(user)
376 assert_equal 2, parent.descendants.count
385 assert_equal 2, parent.descendants.count
377 assert_equal 2, parent.descendants.visible(user).count
386 assert_equal 2, parent.descendants.visible(user).count
378 # awesome_nested_set 2-1-stable branch has regression.
387 # awesome_nested_set 2-1-stable branch has regression.
379 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
388 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
380 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
389 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
381 assert_equal 2, parent.descendants.collect{|i| i}.size
390 assert_equal 2, parent.descendants.collect{|i| i}.size
382 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
391 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
383 end
392 end
384
393
385 def test_visible_scope_with_unsaved_user_should_not_raise_an_error
394 def test_visible_scope_with_unsaved_user_should_not_raise_an_error
386 user = User.new
395 user = User.new
387 assert_nothing_raised do
396 assert_nothing_raised do
388 Issue.visible(user).to_a
397 Issue.visible(user).to_a
389 end
398 end
390 end
399 end
391
400
392 def test_open_scope
401 def test_open_scope
393 issues = Issue.open.to_a
402 issues = Issue.open.to_a
394 assert_nil issues.detect(&:closed?)
403 assert_nil issues.detect(&:closed?)
395 end
404 end
396
405
397 def test_open_scope_with_arg
406 def test_open_scope_with_arg
398 issues = Issue.open(false).to_a
407 issues = Issue.open(false).to_a
399 assert_equal issues, issues.select(&:closed?)
408 assert_equal issues, issues.select(&:closed?)
400 end
409 end
401
410
402 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
411 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
403 version = Version.find(2)
412 version = Version.find(2)
404 assert version.fixed_issues.any?
413 assert version.fixed_issues.any?
405 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
414 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
406 end
415 end
407
416
408 def test_fixed_version_scope_with_empty_array_should_return_no_result
417 def test_fixed_version_scope_with_empty_array_should_return_no_result
409 assert_equal 0, Issue.fixed_version([]).count
418 assert_equal 0, Issue.fixed_version([]).count
410 end
419 end
411
420
412 def test_errors_full_messages_should_include_custom_fields_errors
421 def test_errors_full_messages_should_include_custom_fields_errors
413 field = IssueCustomField.find_by_name('Database')
422 field = IssueCustomField.find_by_name('Database')
414
423
415 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
424 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
416 :status_id => 1, :subject => 'test_create',
425 :status_id => 1, :subject => 'test_create',
417 :description => 'IssueTest#test_create_with_required_custom_field')
426 :description => 'IssueTest#test_create_with_required_custom_field')
418 assert issue.available_custom_fields.include?(field)
427 assert issue.available_custom_fields.include?(field)
419 # Invalid value
428 # Invalid value
420 issue.custom_field_values = { field.id => 'SQLServer' }
429 issue.custom_field_values = { field.id => 'SQLServer' }
421
430
422 assert !issue.valid?
431 assert !issue.valid?
423 assert_equal 1, issue.errors.full_messages.size
432 assert_equal 1, issue.errors.full_messages.size
424 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
433 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
425 issue.errors.full_messages.first
434 issue.errors.full_messages.first
426 end
435 end
427
436
428 def test_update_issue_with_required_custom_field
437 def test_update_issue_with_required_custom_field
429 field = IssueCustomField.find_by_name('Database')
438 field = IssueCustomField.find_by_name('Database')
430 field.update_attribute(:is_required, true)
439 field.update_attribute(:is_required, true)
431
440
432 issue = Issue.find(1)
441 issue = Issue.find(1)
433 assert_nil issue.custom_value_for(field)
442 assert_nil issue.custom_value_for(field)
434 assert issue.available_custom_fields.include?(field)
443 assert issue.available_custom_fields.include?(field)
435 # No change to custom values, issue can be saved
444 # No change to custom values, issue can be saved
436 assert issue.save
445 assert issue.save
437 # Blank value
446 # Blank value
438 issue.custom_field_values = { field.id => '' }
447 issue.custom_field_values = { field.id => '' }
439 assert !issue.save
448 assert !issue.save
440 # Valid value
449 # Valid value
441 issue.custom_field_values = { field.id => 'PostgreSQL' }
450 issue.custom_field_values = { field.id => 'PostgreSQL' }
442 assert issue.save
451 assert issue.save
443 issue.reload
452 issue.reload
444 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
453 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
445 end
454 end
446
455
447 def test_should_not_update_attributes_if_custom_fields_validation_fails
456 def test_should_not_update_attributes_if_custom_fields_validation_fails
448 issue = Issue.find(1)
457 issue = Issue.find(1)
449 field = IssueCustomField.find_by_name('Database')
458 field = IssueCustomField.find_by_name('Database')
450 assert issue.available_custom_fields.include?(field)
459 assert issue.available_custom_fields.include?(field)
451
460
452 issue.custom_field_values = { field.id => 'Invalid' }
461 issue.custom_field_values = { field.id => 'Invalid' }
453 issue.subject = 'Should be not be saved'
462 issue.subject = 'Should be not be saved'
454 assert !issue.save
463 assert !issue.save
455
464
456 issue.reload
465 issue.reload
457 assert_equal "Cannot print recipes", issue.subject
466 assert_equal "Cannot print recipes", issue.subject
458 end
467 end
459
468
460 def test_should_not_recreate_custom_values_objects_on_update
469 def test_should_not_recreate_custom_values_objects_on_update
461 field = IssueCustomField.find_by_name('Database')
470 field = IssueCustomField.find_by_name('Database')
462
471
463 issue = Issue.find(1)
472 issue = Issue.find(1)
464 issue.custom_field_values = { field.id => 'PostgreSQL' }
473 issue.custom_field_values = { field.id => 'PostgreSQL' }
465 assert issue.save
474 assert issue.save
466 custom_value = issue.custom_value_for(field)
475 custom_value = issue.custom_value_for(field)
467 issue.reload
476 issue.reload
468 issue.custom_field_values = { field.id => 'MySQL' }
477 issue.custom_field_values = { field.id => 'MySQL' }
469 assert issue.save
478 assert issue.save
470 issue.reload
479 issue.reload
471 assert_equal custom_value.id, issue.custom_value_for(field).id
480 assert_equal custom_value.id, issue.custom_value_for(field).id
472 end
481 end
473
482
474 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
483 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
475 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
484 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
476 :status_id => 1, :subject => 'Test',
485 :status_id => 1, :subject => 'Test',
477 :custom_field_values => {'2' => 'Test'})
486 :custom_field_values => {'2' => 'Test'})
478 assert !Tracker.find(2).custom_field_ids.include?(2)
487 assert !Tracker.find(2).custom_field_ids.include?(2)
479
488
480 issue = Issue.find(issue.id)
489 issue = Issue.find(issue.id)
481 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
490 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
482
491
483 issue = Issue.find(issue.id)
492 issue = Issue.find(issue.id)
484 custom_value = issue.custom_value_for(2)
493 custom_value = issue.custom_value_for(2)
485 assert_not_nil custom_value
494 assert_not_nil custom_value
486 assert_equal 'Test', custom_value.value
495 assert_equal 'Test', custom_value.value
487 end
496 end
488
497
489 def test_assigning_tracker_id_should_reload_custom_fields_values
498 def test_assigning_tracker_id_should_reload_custom_fields_values
490 issue = Issue.new(:project => Project.find(1))
499 issue = Issue.new(:project => Project.find(1))
491 assert issue.custom_field_values.empty?
500 assert issue.custom_field_values.empty?
492 issue.tracker_id = 1
501 issue.tracker_id = 1
493 assert issue.custom_field_values.any?
502 assert issue.custom_field_values.any?
494 end
503 end
495
504
496 def test_assigning_attributes_should_assign_project_and_tracker_first
505 def test_assigning_attributes_should_assign_project_and_tracker_first
497 seq = sequence('seq')
506 seq = sequence('seq')
498 issue = Issue.new
507 issue = Issue.new
499 issue.expects(:project_id=).in_sequence(seq)
508 issue.expects(:project_id=).in_sequence(seq)
500 issue.expects(:tracker_id=).in_sequence(seq)
509 issue.expects(:tracker_id=).in_sequence(seq)
501 issue.expects(:subject=).in_sequence(seq)
510 issue.expects(:subject=).in_sequence(seq)
502 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
511 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
503 end
512 end
504
513
505 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
514 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
506 attributes = ActiveSupport::OrderedHash.new
515 attributes = ActiveSupport::OrderedHash.new
507 attributes['custom_field_values'] = { '1' => 'MySQL' }
516 attributes['custom_field_values'] = { '1' => 'MySQL' }
508 attributes['tracker_id'] = '1'
517 attributes['tracker_id'] = '1'
509 issue = Issue.new(:project => Project.find(1))
518 issue = Issue.new(:project => Project.find(1))
510 issue.attributes = attributes
519 issue.attributes = attributes
511 assert_equal 'MySQL', issue.custom_field_value(1)
520 assert_equal 'MySQL', issue.custom_field_value(1)
512 end
521 end
513
522
514 def test_changing_tracker_should_clear_disabled_core_fields
523 def test_changing_tracker_should_clear_disabled_core_fields
515 tracker = Tracker.find(2)
524 tracker = Tracker.find(2)
516 tracker.core_fields = tracker.core_fields - %w(due_date)
525 tracker.core_fields = tracker.core_fields - %w(due_date)
517 tracker.save!
526 tracker.save!
518
527
519 issue = Issue.generate!(:tracker_id => 1, :start_date => Date.today, :due_date => Date.today)
528 issue = Issue.generate!(:tracker_id => 1, :start_date => Date.today, :due_date => Date.today)
520 issue.save!
529 issue.save!
521
530
522 issue.tracker_id = 2
531 issue.tracker_id = 2
523 issue.save!
532 issue.save!
524 assert_not_nil issue.start_date
533 assert_not_nil issue.start_date
525 assert_nil issue.due_date
534 assert_nil issue.due_date
526 end
535 end
527
536
528 def test_changing_tracker_should_not_add_cleared_fields_to_journal
537 def test_changing_tracker_should_not_add_cleared_fields_to_journal
529 tracker = Tracker.find(2)
538 tracker = Tracker.find(2)
530 tracker.core_fields = tracker.core_fields - %w(due_date)
539 tracker.core_fields = tracker.core_fields - %w(due_date)
531 tracker.save!
540 tracker.save!
532
541
533 issue = Issue.generate!(:tracker_id => 1, :due_date => Date.today)
542 issue = Issue.generate!(:tracker_id => 1, :due_date => Date.today)
534 issue.save!
543 issue.save!
535
544
536 assert_difference 'Journal.count' do
545 assert_difference 'Journal.count' do
537 issue.init_journal User.find(1)
546 issue.init_journal User.find(1)
538 issue.tracker_id = 2
547 issue.tracker_id = 2
539 issue.save!
548 issue.save!
540 assert_nil issue.due_date
549 assert_nil issue.due_date
541 end
550 end
542 journal = Journal.order('id DESC').first
551 journal = Journal.order('id DESC').first
543 assert_equal 1, journal.details.count
552 assert_equal 1, journal.details.count
544 end
553 end
545
554
546 def test_reload_should_reload_custom_field_values
555 def test_reload_should_reload_custom_field_values
547 issue = Issue.generate!
556 issue = Issue.generate!
548 issue.custom_field_values = {'2' => 'Foo'}
557 issue.custom_field_values = {'2' => 'Foo'}
549 issue.save!
558 issue.save!
550
559
551 issue = Issue.order('id desc').first
560 issue = Issue.order('id desc').first
552 assert_equal 'Foo', issue.custom_field_value(2)
561 assert_equal 'Foo', issue.custom_field_value(2)
553
562
554 issue.custom_field_values = {'2' => 'Bar'}
563 issue.custom_field_values = {'2' => 'Bar'}
555 assert_equal 'Bar', issue.custom_field_value(2)
564 assert_equal 'Bar', issue.custom_field_value(2)
556
565
557 issue.reload
566 issue.reload
558 assert_equal 'Foo', issue.custom_field_value(2)
567 assert_equal 'Foo', issue.custom_field_value(2)
559 end
568 end
560
569
561 def test_should_update_issue_with_disabled_tracker
570 def test_should_update_issue_with_disabled_tracker
562 p = Project.find(1)
571 p = Project.find(1)
563 issue = Issue.find(1)
572 issue = Issue.find(1)
564
573
565 p.trackers.delete(issue.tracker)
574 p.trackers.delete(issue.tracker)
566 assert !p.trackers.include?(issue.tracker)
575 assert !p.trackers.include?(issue.tracker)
567
576
568 issue.reload
577 issue.reload
569 issue.subject = 'New subject'
578 issue.subject = 'New subject'
570 assert issue.save
579 assert issue.save
571 end
580 end
572
581
573 def test_should_not_set_a_disabled_tracker
582 def test_should_not_set_a_disabled_tracker
574 p = Project.find(1)
583 p = Project.find(1)
575 p.trackers.delete(Tracker.find(2))
584 p.trackers.delete(Tracker.find(2))
576
585
577 issue = Issue.find(1)
586 issue = Issue.find(1)
578 issue.tracker_id = 2
587 issue.tracker_id = 2
579 issue.subject = 'New subject'
588 issue.subject = 'New subject'
580 assert !issue.save
589 assert !issue.save
581 assert_not_equal [], issue.errors[:tracker_id]
590 assert_not_equal [], issue.errors[:tracker_id]
582 end
591 end
583
592
584 def test_category_based_assignment
593 def test_category_based_assignment
585 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
594 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
586 :status_id => 1, :priority => IssuePriority.all.first,
595 :status_id => 1, :priority => IssuePriority.all.first,
587 :subject => 'Assignment test',
596 :subject => 'Assignment test',
588 :description => 'Assignment test', :category_id => 1)
597 :description => 'Assignment test', :category_id => 1)
589 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
598 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
590 end
599 end
591
600
592 def test_new_statuses_allowed_to
601 def test_new_statuses_allowed_to
593 WorkflowTransition.delete_all
602 WorkflowTransition.delete_all
594 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
603 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
595 :old_status_id => 1, :new_status_id => 2,
604 :old_status_id => 1, :new_status_id => 2,
596 :author => false, :assignee => false)
605 :author => false, :assignee => false)
597 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
606 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
598 :old_status_id => 1, :new_status_id => 3,
607 :old_status_id => 1, :new_status_id => 3,
599 :author => true, :assignee => false)
608 :author => true, :assignee => false)
600 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
609 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
601 :old_status_id => 1, :new_status_id => 4,
610 :old_status_id => 1, :new_status_id => 4,
602 :author => false, :assignee => true)
611 :author => false, :assignee => true)
603 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
612 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
604 :old_status_id => 1, :new_status_id => 5,
613 :old_status_id => 1, :new_status_id => 5,
605 :author => true, :assignee => true)
614 :author => true, :assignee => true)
606 status = IssueStatus.find(1)
615 status = IssueStatus.find(1)
607 role = Role.find(1)
616 role = Role.find(1)
608 tracker = Tracker.find(1)
617 tracker = Tracker.find(1)
609 user = User.find(2)
618 user = User.find(2)
610
619
611 issue = Issue.generate!(:tracker => tracker, :status => status,
620 issue = Issue.generate!(:tracker => tracker, :status => status,
612 :project_id => 1, :author_id => 1)
621 :project_id => 1, :author_id => 1)
613 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
622 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
614
623
615 issue = Issue.generate!(:tracker => tracker, :status => status,
624 issue = Issue.generate!(:tracker => tracker, :status => status,
616 :project_id => 1, :author => user)
625 :project_id => 1, :author => user)
617 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
626 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
618
627
619 issue = Issue.generate!(:tracker => tracker, :status => status,
628 issue = Issue.generate!(:tracker => tracker, :status => status,
620 :project_id => 1, :author_id => 1,
629 :project_id => 1, :author_id => 1,
621 :assigned_to => user)
630 :assigned_to => user)
622 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
631 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
623
632
624 issue = Issue.generate!(:tracker => tracker, :status => status,
633 issue = Issue.generate!(:tracker => tracker, :status => status,
625 :project_id => 1, :author => user,
634 :project_id => 1, :author => user,
626 :assigned_to => user)
635 :assigned_to => user)
627 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
636 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
628
637
629 group = Group.generate!
638 group = Group.generate!
630 group.users << user
639 group.users << user
631 issue = Issue.generate!(:tracker => tracker, :status => status,
640 issue = Issue.generate!(:tracker => tracker, :status => status,
632 :project_id => 1, :author => user,
641 :project_id => 1, :author => user,
633 :assigned_to => group)
642 :assigned_to => group)
634 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
643 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
635 end
644 end
636
645
637 def test_new_statuses_allowed_to_should_consider_group_assignment
646 def test_new_statuses_allowed_to_should_consider_group_assignment
638 WorkflowTransition.delete_all
647 WorkflowTransition.delete_all
639 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
648 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
640 :old_status_id => 1, :new_status_id => 4,
649 :old_status_id => 1, :new_status_id => 4,
641 :author => false, :assignee => true)
650 :author => false, :assignee => true)
642 user = User.find(2)
651 user = User.find(2)
643 group = Group.generate!
652 group = Group.generate!
644 group.users << user
653 group.users << user
645
654
646 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
655 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
647 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
656 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
648 end
657 end
649
658
650 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
659 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
651 admin = User.find(1)
660 admin = User.find(1)
652 issue = Issue.find(1)
661 issue = Issue.find(1)
653 assert !admin.member_of?(issue.project)
662 assert !admin.member_of?(issue.project)
654 expected_statuses = [issue.status] +
663 expected_statuses = [issue.status] +
655 WorkflowTransition.where(:old_status_id => issue.status_id).
664 WorkflowTransition.where(:old_status_id => issue.status_id).
656 map(&:new_status).uniq.sort
665 map(&:new_status).uniq.sort
657 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
666 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
658 end
667 end
659
668
660 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
669 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
661 issue = Issue.find(1).copy
670 issue = Issue.find(1).copy
662 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
671 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
663
672
664 issue = Issue.find(2).copy
673 issue = Issue.find(2).copy
665 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
674 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
666 end
675 end
667
676
668 def test_safe_attributes_names_should_not_include_disabled_field
677 def test_safe_attributes_names_should_not_include_disabled_field
669 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
678 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
670
679
671 issue = Issue.new(:tracker => tracker)
680 issue = Issue.new(:tracker => tracker)
672 assert_include 'tracker_id', issue.safe_attribute_names
681 assert_include 'tracker_id', issue.safe_attribute_names
673 assert_include 'status_id', issue.safe_attribute_names
682 assert_include 'status_id', issue.safe_attribute_names
674 assert_include 'subject', issue.safe_attribute_names
683 assert_include 'subject', issue.safe_attribute_names
675 assert_include 'description', issue.safe_attribute_names
684 assert_include 'description', issue.safe_attribute_names
676 assert_include 'custom_field_values', issue.safe_attribute_names
685 assert_include 'custom_field_values', issue.safe_attribute_names
677 assert_include 'custom_fields', issue.safe_attribute_names
686 assert_include 'custom_fields', issue.safe_attribute_names
678 assert_include 'lock_version', issue.safe_attribute_names
687 assert_include 'lock_version', issue.safe_attribute_names
679
688
680 tracker.core_fields.each do |field|
689 tracker.core_fields.each do |field|
681 assert_include field, issue.safe_attribute_names
690 assert_include field, issue.safe_attribute_names
682 end
691 end
683
692
684 tracker.disabled_core_fields.each do |field|
693 tracker.disabled_core_fields.each do |field|
685 assert_not_include field, issue.safe_attribute_names
694 assert_not_include field, issue.safe_attribute_names
686 end
695 end
687 end
696 end
688
697
689 def test_safe_attributes_should_ignore_disabled_fields
698 def test_safe_attributes_should_ignore_disabled_fields
690 tracker = Tracker.find(1)
699 tracker = Tracker.find(1)
691 tracker.core_fields = %w(assigned_to_id due_date)
700 tracker.core_fields = %w(assigned_to_id due_date)
692 tracker.save!
701 tracker.save!
693
702
694 issue = Issue.new(:tracker => tracker)
703 issue = Issue.new(:tracker => tracker)
695 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
704 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
696 assert_nil issue.start_date
705 assert_nil issue.start_date
697 assert_equal Date.parse('2012-07-14'), issue.due_date
706 assert_equal Date.parse('2012-07-14'), issue.due_date
698 end
707 end
699
708
700 def test_safe_attributes_should_accept_target_tracker_enabled_fields
709 def test_safe_attributes_should_accept_target_tracker_enabled_fields
701 source = Tracker.find(1)
710 source = Tracker.find(1)
702 source.core_fields = []
711 source.core_fields = []
703 source.save!
712 source.save!
704 target = Tracker.find(2)
713 target = Tracker.find(2)
705 target.core_fields = %w(assigned_to_id due_date)
714 target.core_fields = %w(assigned_to_id due_date)
706 target.save!
715 target.save!
707
716
708 issue = Issue.new(:tracker => source)
717 issue = Issue.new(:tracker => source)
709 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
718 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
710 assert_equal target, issue.tracker
719 assert_equal target, issue.tracker
711 assert_equal Date.parse('2012-07-14'), issue.due_date
720 assert_equal Date.parse('2012-07-14'), issue.due_date
712 end
721 end
713
722
714 def test_safe_attributes_should_not_include_readonly_fields
723 def test_safe_attributes_should_not_include_readonly_fields
715 WorkflowPermission.delete_all
724 WorkflowPermission.delete_all
716 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
725 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
717 :role_id => 1, :field_name => 'due_date',
726 :role_id => 1, :field_name => 'due_date',
718 :rule => 'readonly')
727 :rule => 'readonly')
719 user = User.find(2)
728 user = User.find(2)
720
729
721 issue = Issue.new(:project_id => 1, :tracker_id => 1)
730 issue = Issue.new(:project_id => 1, :tracker_id => 1)
722 assert_equal %w(due_date), issue.read_only_attribute_names(user)
731 assert_equal %w(due_date), issue.read_only_attribute_names(user)
723 assert_not_include 'due_date', issue.safe_attribute_names(user)
732 assert_not_include 'due_date', issue.safe_attribute_names(user)
724
733
725 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
734 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
726 assert_equal Date.parse('2012-07-14'), issue.start_date
735 assert_equal Date.parse('2012-07-14'), issue.start_date
727 assert_nil issue.due_date
736 assert_nil issue.due_date
728 end
737 end
729
738
730 def test_safe_attributes_should_not_include_readonly_custom_fields
739 def test_safe_attributes_should_not_include_readonly_custom_fields
731 cf1 = IssueCustomField.create!(:name => 'Writable field',
740 cf1 = IssueCustomField.create!(:name => 'Writable field',
732 :field_format => 'string',
741 :field_format => 'string',
733 :is_for_all => true, :tracker_ids => [1])
742 :is_for_all => true, :tracker_ids => [1])
734 cf2 = IssueCustomField.create!(:name => 'Readonly field',
743 cf2 = IssueCustomField.create!(:name => 'Readonly field',
735 :field_format => 'string',
744 :field_format => 'string',
736 :is_for_all => true, :tracker_ids => [1])
745 :is_for_all => true, :tracker_ids => [1])
737 WorkflowPermission.delete_all
746 WorkflowPermission.delete_all
738 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
747 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
739 :role_id => 1, :field_name => cf2.id.to_s,
748 :role_id => 1, :field_name => cf2.id.to_s,
740 :rule => 'readonly')
749 :rule => 'readonly')
741 user = User.find(2)
750 user = User.find(2)
742 issue = Issue.new(:project_id => 1, :tracker_id => 1)
751 issue = Issue.new(:project_id => 1, :tracker_id => 1)
743 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
752 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
744 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
753 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
745
754
746 issue.send :safe_attributes=, {'custom_field_values' => {
755 issue.send :safe_attributes=, {'custom_field_values' => {
747 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
756 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
748 }}, user
757 }}, user
749 assert_equal 'value1', issue.custom_field_value(cf1)
758 assert_equal 'value1', issue.custom_field_value(cf1)
750 assert_nil issue.custom_field_value(cf2)
759 assert_nil issue.custom_field_value(cf2)
751
760
752 issue.send :safe_attributes=, {'custom_fields' => [
761 issue.send :safe_attributes=, {'custom_fields' => [
753 {'id' => cf1.id.to_s, 'value' => 'valuea'},
762 {'id' => cf1.id.to_s, 'value' => 'valuea'},
754 {'id' => cf2.id.to_s, 'value' => 'valueb'}
763 {'id' => cf2.id.to_s, 'value' => 'valueb'}
755 ]}, user
764 ]}, user
756 assert_equal 'valuea', issue.custom_field_value(cf1)
765 assert_equal 'valuea', issue.custom_field_value(cf1)
757 assert_nil issue.custom_field_value(cf2)
766 assert_nil issue.custom_field_value(cf2)
758 end
767 end
759
768
760 def test_editable_custom_field_values_should_return_non_readonly_custom_values
769 def test_editable_custom_field_values_should_return_non_readonly_custom_values
761 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
770 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
762 :is_for_all => true, :tracker_ids => [1, 2])
771 :is_for_all => true, :tracker_ids => [1, 2])
763 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
772 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
764 :is_for_all => true, :tracker_ids => [1, 2])
773 :is_for_all => true, :tracker_ids => [1, 2])
765 WorkflowPermission.delete_all
774 WorkflowPermission.delete_all
766 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
775 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
767 :field_name => cf2.id.to_s, :rule => 'readonly')
776 :field_name => cf2.id.to_s, :rule => 'readonly')
768 user = User.find(2)
777 user = User.find(2)
769
778
770 issue = Issue.new(:project_id => 1, :tracker_id => 1)
779 issue = Issue.new(:project_id => 1, :tracker_id => 1)
771 values = issue.editable_custom_field_values(user)
780 values = issue.editable_custom_field_values(user)
772 assert values.detect {|value| value.custom_field == cf1}
781 assert values.detect {|value| value.custom_field == cf1}
773 assert_nil values.detect {|value| value.custom_field == cf2}
782 assert_nil values.detect {|value| value.custom_field == cf2}
774
783
775 issue.tracker_id = 2
784 issue.tracker_id = 2
776 values = issue.editable_custom_field_values(user)
785 values = issue.editable_custom_field_values(user)
777 assert values.detect {|value| value.custom_field == cf1}
786 assert values.detect {|value| value.custom_field == cf1}
778 assert values.detect {|value| value.custom_field == cf2}
787 assert values.detect {|value| value.custom_field == cf2}
779 end
788 end
780
789
781 def test_editable_custom_fields_should_return_custom_field_that_is_enabled_for_the_role_only
790 def test_editable_custom_fields_should_return_custom_field_that_is_enabled_for_the_role_only
782 enabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
791 enabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
783 disabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
792 disabled_cf = IssueCustomField.generate!(:is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
784 user = User.find(2)
793 user = User.find(2)
785 issue = Issue.new(:project_id => 1, :tracker_id => 1)
794 issue = Issue.new(:project_id => 1, :tracker_id => 1)
786
795
787 assert_include enabled_cf, issue.editable_custom_fields(user)
796 assert_include enabled_cf, issue.editable_custom_fields(user)
788 assert_not_include disabled_cf, issue.editable_custom_fields(user)
797 assert_not_include disabled_cf, issue.editable_custom_fields(user)
789 end
798 end
790
799
791 def test_safe_attributes_should_accept_target_tracker_writable_fields
800 def test_safe_attributes_should_accept_target_tracker_writable_fields
792 WorkflowPermission.delete_all
801 WorkflowPermission.delete_all
793 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
802 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
794 :role_id => 1, :field_name => 'due_date',
803 :role_id => 1, :field_name => 'due_date',
795 :rule => 'readonly')
804 :rule => 'readonly')
796 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
805 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
797 :role_id => 1, :field_name => 'start_date',
806 :role_id => 1, :field_name => 'start_date',
798 :rule => 'readonly')
807 :rule => 'readonly')
799 user = User.find(2)
808 user = User.find(2)
800
809
801 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
810 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
802
811
803 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
812 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
804 'due_date' => '2012-07-14'}, user
813 'due_date' => '2012-07-14'}, user
805 assert_equal Date.parse('2012-07-12'), issue.start_date
814 assert_equal Date.parse('2012-07-12'), issue.start_date
806 assert_nil issue.due_date
815 assert_nil issue.due_date
807
816
808 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
817 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
809 'due_date' => '2012-07-16',
818 'due_date' => '2012-07-16',
810 'tracker_id' => 2}, user
819 'tracker_id' => 2}, user
811 assert_equal Date.parse('2012-07-12'), issue.start_date
820 assert_equal Date.parse('2012-07-12'), issue.start_date
812 assert_equal Date.parse('2012-07-16'), issue.due_date
821 assert_equal Date.parse('2012-07-16'), issue.due_date
813 end
822 end
814
823
815 def test_safe_attributes_should_accept_target_status_writable_fields
824 def test_safe_attributes_should_accept_target_status_writable_fields
816 WorkflowPermission.delete_all
825 WorkflowPermission.delete_all
817 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
826 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
818 :role_id => 1, :field_name => 'due_date',
827 :role_id => 1, :field_name => 'due_date',
819 :rule => 'readonly')
828 :rule => 'readonly')
820 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
829 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
821 :role_id => 1, :field_name => 'start_date',
830 :role_id => 1, :field_name => 'start_date',
822 :rule => 'readonly')
831 :rule => 'readonly')
823 user = User.find(2)
832 user = User.find(2)
824
833
825 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
834 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
826
835
827 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
836 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
828 'due_date' => '2012-07-14'},
837 'due_date' => '2012-07-14'},
829 user
838 user
830 assert_equal Date.parse('2012-07-12'), issue.start_date
839 assert_equal Date.parse('2012-07-12'), issue.start_date
831 assert_nil issue.due_date
840 assert_nil issue.due_date
832
841
833 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
842 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
834 'due_date' => '2012-07-16',
843 'due_date' => '2012-07-16',
835 'status_id' => 2},
844 'status_id' => 2},
836 user
845 user
837 assert_equal Date.parse('2012-07-12'), issue.start_date
846 assert_equal Date.parse('2012-07-12'), issue.start_date
838 assert_equal Date.parse('2012-07-16'), issue.due_date
847 assert_equal Date.parse('2012-07-16'), issue.due_date
839 end
848 end
840
849
841 def test_required_attributes_should_be_validated
850 def test_required_attributes_should_be_validated
842 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
851 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
843 :is_for_all => true, :tracker_ids => [1, 2])
852 :is_for_all => true, :tracker_ids => [1, 2])
844
853
845 WorkflowPermission.delete_all
854 WorkflowPermission.delete_all
846 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
855 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
847 :role_id => 1, :field_name => 'due_date',
856 :role_id => 1, :field_name => 'due_date',
848 :rule => 'required')
857 :rule => 'required')
849 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
858 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
850 :role_id => 1, :field_name => 'category_id',
859 :role_id => 1, :field_name => 'category_id',
851 :rule => 'required')
860 :rule => 'required')
852 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
861 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
853 :role_id => 1, :field_name => cf.id.to_s,
862 :role_id => 1, :field_name => cf.id.to_s,
854 :rule => 'required')
863 :rule => 'required')
855
864
856 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
865 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
857 :role_id => 1, :field_name => 'start_date',
866 :role_id => 1, :field_name => 'start_date',
858 :rule => 'required')
867 :rule => 'required')
859 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
868 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
860 :role_id => 1, :field_name => cf.id.to_s,
869 :role_id => 1, :field_name => cf.id.to_s,
861 :rule => 'required')
870 :rule => 'required')
862 user = User.find(2)
871 user = User.find(2)
863
872
864 issue = Issue.new(:project_id => 1, :tracker_id => 1,
873 issue = Issue.new(:project_id => 1, :tracker_id => 1,
865 :status_id => 1, :subject => 'Required fields',
874 :status_id => 1, :subject => 'Required fields',
866 :author => user)
875 :author => user)
867 assert_equal [cf.id.to_s, "category_id", "due_date"],
876 assert_equal [cf.id.to_s, "category_id", "due_date"],
868 issue.required_attribute_names(user).sort
877 issue.required_attribute_names(user).sort
869 assert !issue.save, "Issue was saved"
878 assert !issue.save, "Issue was saved"
870 assert_equal ["Category cannot be blank", "Due date cannot be blank", "Foo cannot be blank"],
879 assert_equal ["Category cannot be blank", "Due date cannot be blank", "Foo cannot be blank"],
871 issue.errors.full_messages.sort
880 issue.errors.full_messages.sort
872
881
873 issue.tracker_id = 2
882 issue.tracker_id = 2
874 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
883 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
875 assert !issue.save, "Issue was saved"
884 assert !issue.save, "Issue was saved"
876 assert_equal ["Foo cannot be blank", "Start date cannot be blank"],
885 assert_equal ["Foo cannot be blank", "Start date cannot be blank"],
877 issue.errors.full_messages.sort
886 issue.errors.full_messages.sort
878
887
879 issue.start_date = Date.today
888 issue.start_date = Date.today
880 issue.custom_field_values = {cf.id.to_s => 'bar'}
889 issue.custom_field_values = {cf.id.to_s => 'bar'}
881 assert issue.save
890 assert issue.save
882 end
891 end
883
892
884 def test_required_attribute_that_is_disabled_for_the_tracker_should_not_be_required
893 def test_required_attribute_that_is_disabled_for_the_tracker_should_not_be_required
885 WorkflowPermission.delete_all
894 WorkflowPermission.delete_all
886 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
895 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
887 :role_id => 1, :field_name => 'start_date',
896 :role_id => 1, :field_name => 'start_date',
888 :rule => 'required')
897 :rule => 'required')
889 user = User.find(2)
898 user = User.find(2)
890
899
891 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
900 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
892 :subject => 'Required fields', :author => user)
901 :subject => 'Required fields', :author => user)
893 assert !issue.save
902 assert !issue.save
894 assert_include "Start date cannot be blank", issue.errors.full_messages
903 assert_include "Start date cannot be blank", issue.errors.full_messages
895
904
896 tracker = Tracker.find(1)
905 tracker = Tracker.find(1)
897 tracker.core_fields -= %w(start_date)
906 tracker.core_fields -= %w(start_date)
898 tracker.save!
907 tracker.save!
899 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
908 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
900 :subject => 'Required fields', :author => user)
909 :subject => 'Required fields', :author => user)
901 assert issue.save
910 assert issue.save
902 end
911 end
903
912
904 def test_required_custom_field_that_is_not_visible_for_the_user_should_not_be_required
913 def test_required_custom_field_that_is_not_visible_for_the_user_should_not_be_required
905 CustomField.delete_all
914 CustomField.delete_all
906 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
915 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
907 user = User.generate!
916 user = User.generate!
908 User.add_to_project(user, Project.find(1), Role.find(2))
917 User.add_to_project(user, Project.find(1), Role.find(2))
909
918
910 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
919 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
911 :subject => 'Required fields', :author => user)
920 :subject => 'Required fields', :author => user)
912 assert_save issue
921 assert_save issue
913 end
922 end
914
923
915 def test_required_custom_field_that_is_visible_for_the_user_should_be_required
924 def test_required_custom_field_that_is_visible_for_the_user_should_be_required
916 CustomField.delete_all
925 CustomField.delete_all
917 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
926 field = IssueCustomField.generate!(:is_required => true, :visible => false, :role_ids => [1], :trackers => Tracker.all, :is_for_all => true)
918 user = User.generate!
927 user = User.generate!
919 User.add_to_project(user, Project.find(1), Role.find(1))
928 User.add_to_project(user, Project.find(1), Role.find(1))
920
929
921 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
930 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
922 :subject => 'Required fields', :author => user)
931 :subject => 'Required fields', :author => user)
923 assert !issue.save
932 assert !issue.save
924 assert_include "#{field.name} cannot be blank", issue.errors.full_messages
933 assert_include "#{field.name} cannot be blank", issue.errors.full_messages
925 end
934 end
926
935
927 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
936 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
928 WorkflowPermission.delete_all
937 WorkflowPermission.delete_all
929 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
938 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
930 :role_id => 1, :field_name => 'due_date',
939 :role_id => 1, :field_name => 'due_date',
931 :rule => 'required')
940 :rule => 'required')
932 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
941 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
933 :role_id => 1, :field_name => 'start_date',
942 :role_id => 1, :field_name => 'start_date',
934 :rule => 'required')
943 :rule => 'required')
935 user = User.find(2)
944 user = User.find(2)
936 member = Member.find(1)
945 member = Member.find(1)
937 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
946 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
938
947
939 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
948 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
940
949
941 member.role_ids = [1, 2]
950 member.role_ids = [1, 2]
942 member.save!
951 member.save!
943 assert_equal [], issue.required_attribute_names(user.reload)
952 assert_equal [], issue.required_attribute_names(user.reload)
944
953
945 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
954 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
946 :role_id => 2, :field_name => 'due_date',
955 :role_id => 2, :field_name => 'due_date',
947 :rule => 'required')
956 :rule => 'required')
948 assert_equal %w(due_date), issue.required_attribute_names(user)
957 assert_equal %w(due_date), issue.required_attribute_names(user)
949
958
950 member.role_ids = [1, 2, 3]
959 member.role_ids = [1, 2, 3]
951 member.save!
960 member.save!
952 assert_equal [], issue.required_attribute_names(user.reload)
961 assert_equal [], issue.required_attribute_names(user.reload)
953
962
954 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
963 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
955 :role_id => 3, :field_name => 'due_date',
964 :role_id => 3, :field_name => 'due_date',
956 :rule => 'readonly')
965 :rule => 'readonly')
957 # required + readonly => required
966 # required + readonly => required
958 assert_equal %w(due_date), issue.required_attribute_names(user)
967 assert_equal %w(due_date), issue.required_attribute_names(user)
959 end
968 end
960
969
961 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
970 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
962 WorkflowPermission.delete_all
971 WorkflowPermission.delete_all
963 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
972 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
964 :role_id => 1, :field_name => 'due_date',
973 :role_id => 1, :field_name => 'due_date',
965 :rule => 'readonly')
974 :rule => 'readonly')
966 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
975 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
967 :role_id => 1, :field_name => 'start_date',
976 :role_id => 1, :field_name => 'start_date',
968 :rule => 'readonly')
977 :rule => 'readonly')
969 user = User.find(2)
978 user = User.find(2)
970 member = Member.find(1)
979 member = Member.find(1)
971 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
980 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
972
981
973 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
982 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
974
983
975 member.role_ids = [1, 2]
984 member.role_ids = [1, 2]
976 member.save!
985 member.save!
977 assert_equal [], issue.read_only_attribute_names(user.reload)
986 assert_equal [], issue.read_only_attribute_names(user.reload)
978
987
979 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
988 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
980 :role_id => 2, :field_name => 'due_date',
989 :role_id => 2, :field_name => 'due_date',
981 :rule => 'readonly')
990 :rule => 'readonly')
982 assert_equal %w(due_date), issue.read_only_attribute_names(user)
991 assert_equal %w(due_date), issue.read_only_attribute_names(user)
983 end
992 end
984
993
985 # A field that is not visible by role 2 and readonly by role 1 should be readonly for user with role 1 and 2
994 # A field that is not visible by role 2 and readonly by role 1 should be readonly for user with role 1 and 2
986 def test_read_only_attribute_names_should_include_custom_fields_that_combine_readonly_and_not_visible_for_roles
995 def test_read_only_attribute_names_should_include_custom_fields_that_combine_readonly_and_not_visible_for_roles
987 field = IssueCustomField.generate!(
996 field = IssueCustomField.generate!(
988 :is_for_all => true, :trackers => Tracker.all, :visible => false, :role_ids => [1]
997 :is_for_all => true, :trackers => Tracker.all, :visible => false, :role_ids => [1]
989 )
998 )
990 WorkflowPermission.delete_all
999 WorkflowPermission.delete_all
991 WorkflowPermission.create!(
1000 WorkflowPermission.create!(
992 :old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => field.id, :rule => 'readonly'
1001 :old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => field.id, :rule => 'readonly'
993 )
1002 )
994 user = User.generate!
1003 user = User.generate!
995 project = Project.find(1)
1004 project = Project.find(1)
996 User.add_to_project(user, project, Role.where(:id => [1, 2]))
1005 User.add_to_project(user, project, Role.where(:id => [1, 2]))
997
1006
998 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1007 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
999 assert_equal [field.id.to_s], issue.read_only_attribute_names(user)
1008 assert_equal [field.id.to_s], issue.read_only_attribute_names(user)
1000 end
1009 end
1001
1010
1002 def test_workflow_rules_should_ignore_roles_without_issue_permissions
1011 def test_workflow_rules_should_ignore_roles_without_issue_permissions
1003 role = Role.generate! :permissions => [:view_issues, :edit_issues]
1012 role = Role.generate! :permissions => [:view_issues, :edit_issues]
1004 ignored_role = Role.generate! :permissions => [:view_issues]
1013 ignored_role = Role.generate! :permissions => [:view_issues]
1005
1014
1006 WorkflowPermission.delete_all
1015 WorkflowPermission.delete_all
1007 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1016 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1008 :role => role, :field_name => 'due_date',
1017 :role => role, :field_name => 'due_date',
1009 :rule => 'required')
1018 :rule => 'required')
1010 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1019 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1011 :role => role, :field_name => 'start_date',
1020 :role => role, :field_name => 'start_date',
1012 :rule => 'readonly')
1021 :rule => 'readonly')
1013 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1022 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1014 :role => role, :field_name => 'done_ratio',
1023 :role => role, :field_name => 'done_ratio',
1015 :rule => 'readonly')
1024 :rule => 'readonly')
1016 user = User.generate!
1025 user = User.generate!
1017 User.add_to_project user, Project.find(1), [role, ignored_role]
1026 User.add_to_project user, Project.find(1), [role, ignored_role]
1018
1027
1019 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1028 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1020
1029
1021 assert_equal %w(due_date), issue.required_attribute_names(user)
1030 assert_equal %w(due_date), issue.required_attribute_names(user)
1022 assert_equal %w(done_ratio start_date), issue.read_only_attribute_names(user).sort
1031 assert_equal %w(done_ratio start_date), issue.read_only_attribute_names(user).sort
1023 end
1032 end
1024
1033
1025 def test_workflow_rules_should_work_for_member_with_duplicate_role
1034 def test_workflow_rules_should_work_for_member_with_duplicate_role
1026 WorkflowPermission.delete_all
1035 WorkflowPermission.delete_all
1027 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1036 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1028 :role_id => 1, :field_name => 'due_date',
1037 :role_id => 1, :field_name => 'due_date',
1029 :rule => 'required')
1038 :rule => 'required')
1030 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1039 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
1031 :role_id => 1, :field_name => 'start_date',
1040 :role_id => 1, :field_name => 'start_date',
1032 :rule => 'readonly')
1041 :rule => 'readonly')
1033
1042
1034 user = User.generate!
1043 user = User.generate!
1035 m = Member.new(:user_id => user.id, :project_id => 1)
1044 m = Member.new(:user_id => user.id, :project_id => 1)
1036 m.member_roles.build(:role_id => 1)
1045 m.member_roles.build(:role_id => 1)
1037 m.member_roles.build(:role_id => 1)
1046 m.member_roles.build(:role_id => 1)
1038 m.save!
1047 m.save!
1039
1048
1040 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1049 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
1041
1050
1042 assert_equal %w(due_date), issue.required_attribute_names(user)
1051 assert_equal %w(due_date), issue.required_attribute_names(user)
1043 assert_equal %w(start_date), issue.read_only_attribute_names(user)
1052 assert_equal %w(start_date), issue.read_only_attribute_names(user)
1044 end
1053 end
1045
1054
1046 def test_copy
1055 def test_copy
1047 issue = Issue.new.copy_from(1)
1056 issue = Issue.new.copy_from(1)
1048 assert issue.copy?
1057 assert issue.copy?
1049 assert issue.save
1058 assert issue.save
1050 issue.reload
1059 issue.reload
1051 orig = Issue.find(1)
1060 orig = Issue.find(1)
1052 assert_equal orig.subject, issue.subject
1061 assert_equal orig.subject, issue.subject
1053 assert_equal orig.tracker, issue.tracker
1062 assert_equal orig.tracker, issue.tracker
1054 assert_equal "125", issue.custom_value_for(2).value
1063 assert_equal "125", issue.custom_value_for(2).value
1055 end
1064 end
1056
1065
1057 def test_copy_should_copy_status
1066 def test_copy_should_copy_status
1058 orig = Issue.find(8)
1067 orig = Issue.find(8)
1059 assert orig.status != orig.default_status
1068 assert orig.status != orig.default_status
1060
1069
1061 issue = Issue.new.copy_from(orig)
1070 issue = Issue.new.copy_from(orig)
1062 assert issue.save
1071 assert issue.save
1063 issue.reload
1072 issue.reload
1064 assert_equal orig.status, issue.status
1073 assert_equal orig.status, issue.status
1065 end
1074 end
1066
1075
1067 def test_copy_should_add_relation_with_copied_issue
1076 def test_copy_should_add_relation_with_copied_issue
1068 copied = Issue.find(1)
1077 copied = Issue.find(1)
1069 issue = Issue.new.copy_from(copied)
1078 issue = Issue.new.copy_from(copied)
1070 assert issue.save
1079 assert issue.save
1071 issue.reload
1080 issue.reload
1072
1081
1073 assert_equal 1, issue.relations.size
1082 assert_equal 1, issue.relations.size
1074 relation = issue.relations.first
1083 relation = issue.relations.first
1075 assert_equal 'copied_to', relation.relation_type
1084 assert_equal 'copied_to', relation.relation_type
1076 assert_equal copied, relation.issue_from
1085 assert_equal copied, relation.issue_from
1077 assert_equal issue, relation.issue_to
1086 assert_equal issue, relation.issue_to
1078 end
1087 end
1079
1088
1080 def test_copy_should_copy_subtasks
1089 def test_copy_should_copy_subtasks
1081 issue = Issue.generate_with_descendants!
1090 issue = Issue.generate_with_descendants!
1082
1091
1083 copy = issue.reload.copy
1092 copy = issue.reload.copy
1084 copy.author = User.find(7)
1093 copy.author = User.find(7)
1085 assert_difference 'Issue.count', 1+issue.descendants.count do
1094 assert_difference 'Issue.count', 1+issue.descendants.count do
1086 assert copy.save
1095 assert copy.save
1087 end
1096 end
1088 copy.reload
1097 copy.reload
1089 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
1098 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
1090 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1099 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1091 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
1100 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
1092 assert_equal copy.author, child_copy.author
1101 assert_equal copy.author, child_copy.author
1093 end
1102 end
1094
1103
1095 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
1104 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
1096 parent = Issue.generate!
1105 parent = Issue.generate!
1097 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1106 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1098 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1107 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1099
1108
1100 copy = parent.reload.copy
1109 copy = parent.reload.copy
1101 copy.parent_issue_id = parent.id
1110 copy.parent_issue_id = parent.id
1102 copy.author = User.find(7)
1111 copy.author = User.find(7)
1103 assert_difference 'Issue.count', 3 do
1112 assert_difference 'Issue.count', 3 do
1104 assert copy.save
1113 assert copy.save
1105 end
1114 end
1106 parent.reload
1115 parent.reload
1107 copy.reload
1116 copy.reload
1108 assert_equal parent, copy.parent
1117 assert_equal parent, copy.parent
1109 assert_equal 3, parent.children.count
1118 assert_equal 3, parent.children.count
1110 assert_equal 5, parent.descendants.count
1119 assert_equal 5, parent.descendants.count
1111 assert_equal 2, copy.children.count
1120 assert_equal 2, copy.children.count
1112 assert_equal 2, copy.descendants.count
1121 assert_equal 2, copy.descendants.count
1113 end
1122 end
1114
1123
1115 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
1124 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
1116 parent = Issue.generate!
1125 parent = Issue.generate!
1117 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1126 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
1118 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1127 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
1119
1128
1120 copy = parent.reload.copy
1129 copy = parent.reload.copy
1121 copy.parent_issue_id = child1.id
1130 copy.parent_issue_id = child1.id
1122 copy.author = User.find(7)
1131 copy.author = User.find(7)
1123 assert_difference 'Issue.count', 3 do
1132 assert_difference 'Issue.count', 3 do
1124 assert copy.save
1133 assert copy.save
1125 end
1134 end
1126 parent.reload
1135 parent.reload
1127 child1.reload
1136 child1.reload
1128 copy.reload
1137 copy.reload
1129 assert_equal child1, copy.parent
1138 assert_equal child1, copy.parent
1130 assert_equal 2, parent.children.count
1139 assert_equal 2, parent.children.count
1131 assert_equal 5, parent.descendants.count
1140 assert_equal 5, parent.descendants.count
1132 assert_equal 1, child1.children.count
1141 assert_equal 1, child1.children.count
1133 assert_equal 3, child1.descendants.count
1142 assert_equal 3, child1.descendants.count
1134 assert_equal 2, copy.children.count
1143 assert_equal 2, copy.children.count
1135 assert_equal 2, copy.descendants.count
1144 assert_equal 2, copy.descendants.count
1136 end
1145 end
1137
1146
1138 def test_copy_should_copy_subtasks_to_target_project
1147 def test_copy_should_copy_subtasks_to_target_project
1139 issue = Issue.generate_with_descendants!
1148 issue = Issue.generate_with_descendants!
1140
1149
1141 copy = issue.copy(:project_id => 3)
1150 copy = issue.copy(:project_id => 3)
1142 assert_difference 'Issue.count', 1+issue.descendants.count do
1151 assert_difference 'Issue.count', 1+issue.descendants.count do
1143 assert copy.save
1152 assert copy.save
1144 end
1153 end
1145 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
1154 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
1146 end
1155 end
1147
1156
1148 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
1157 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
1149 issue = Issue.generate_with_descendants!
1158 issue = Issue.generate_with_descendants!
1150
1159
1151 copy = issue.reload.copy
1160 copy = issue.reload.copy
1152 assert_difference 'Issue.count', 1+issue.descendants.count do
1161 assert_difference 'Issue.count', 1+issue.descendants.count do
1153 assert copy.save
1162 assert copy.save
1154 assert copy.save
1163 assert copy.save
1155 end
1164 end
1156 end
1165 end
1157
1166
1158 def test_should_not_call_after_project_change_on_creation
1167 def test_should_not_call_after_project_change_on_creation
1159 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1168 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
1160 :subject => 'Test', :author_id => 1)
1169 :subject => 'Test', :author_id => 1)
1161 issue.expects(:after_project_change).never
1170 issue.expects(:after_project_change).never
1162 issue.save!
1171 issue.save!
1163 end
1172 end
1164
1173
1165 def test_should_not_call_after_project_change_on_update
1174 def test_should_not_call_after_project_change_on_update
1166 issue = Issue.find(1)
1175 issue = Issue.find(1)
1167 issue.project = Project.find(1)
1176 issue.project = Project.find(1)
1168 issue.subject = 'No project change'
1177 issue.subject = 'No project change'
1169 issue.expects(:after_project_change).never
1178 issue.expects(:after_project_change).never
1170 issue.save!
1179 issue.save!
1171 end
1180 end
1172
1181
1173 def test_should_call_after_project_change_on_project_change
1182 def test_should_call_after_project_change_on_project_change
1174 issue = Issue.find(1)
1183 issue = Issue.find(1)
1175 issue.project = Project.find(2)
1184 issue.project = Project.find(2)
1176 issue.expects(:after_project_change).once
1185 issue.expects(:after_project_change).once
1177 issue.save!
1186 issue.save!
1178 end
1187 end
1179
1188
1180 def test_adding_journal_should_update_timestamp
1189 def test_adding_journal_should_update_timestamp
1181 issue = Issue.find(1)
1190 issue = Issue.find(1)
1182 updated_on_was = issue.updated_on
1191 updated_on_was = issue.updated_on
1183
1192
1184 issue.init_journal(User.first, "Adding notes")
1193 issue.init_journal(User.first, "Adding notes")
1185 assert_difference 'Journal.count' do
1194 assert_difference 'Journal.count' do
1186 assert issue.save
1195 assert issue.save
1187 end
1196 end
1188 issue.reload
1197 issue.reload
1189
1198
1190 assert_not_equal updated_on_was, issue.updated_on
1199 assert_not_equal updated_on_was, issue.updated_on
1191 end
1200 end
1192
1201
1193 def test_should_close_duplicates
1202 def test_should_close_duplicates
1194 # Create 3 issues
1203 # Create 3 issues
1195 issue1 = Issue.generate!
1204 issue1 = Issue.generate!
1196 issue2 = Issue.generate!
1205 issue2 = Issue.generate!
1197 issue3 = Issue.generate!
1206 issue3 = Issue.generate!
1198
1207
1199 # 2 is a dupe of 1
1208 # 2 is a dupe of 1
1200 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1209 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1201 :relation_type => IssueRelation::TYPE_DUPLICATES)
1210 :relation_type => IssueRelation::TYPE_DUPLICATES)
1202 # And 3 is a dupe of 2
1211 # And 3 is a dupe of 2
1203 # IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1212 # IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1204 # :relation_type => IssueRelation::TYPE_DUPLICATES)
1213 # :relation_type => IssueRelation::TYPE_DUPLICATES)
1205 # And 3 is a dupe of 1 (circular duplicates)
1214 # And 3 is a dupe of 1 (circular duplicates)
1206 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1215 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1207 :relation_type => IssueRelation::TYPE_DUPLICATES)
1216 :relation_type => IssueRelation::TYPE_DUPLICATES)
1208
1217
1209 assert issue1.reload.duplicates.include?(issue2)
1218 assert issue1.reload.duplicates.include?(issue2)
1210
1219
1211 # Closing issue 1
1220 # Closing issue 1
1212 issue1.init_journal(User.first, "Closing issue1")
1221 issue1.init_journal(User.first, "Closing issue1")
1213 issue1.status = IssueStatus.where(:is_closed => true).first
1222 issue1.status = IssueStatus.where(:is_closed => true).first
1214 assert issue1.save
1223 assert issue1.save
1215 # 2 and 3 should be also closed
1224 # 2 and 3 should be also closed
1216 assert issue2.reload.closed?
1225 assert issue2.reload.closed?
1217 assert issue3.reload.closed?
1226 assert issue3.reload.closed?
1218 end
1227 end
1219
1228
1220 def test_should_not_close_duplicated_issue
1229 def test_should_not_close_duplicated_issue
1221 issue1 = Issue.generate!
1230 issue1 = Issue.generate!
1222 issue2 = Issue.generate!
1231 issue2 = Issue.generate!
1223
1232
1224 # 2 is a dupe of 1
1233 # 2 is a dupe of 1
1225 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1234 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1226 :relation_type => IssueRelation::TYPE_DUPLICATES)
1235 :relation_type => IssueRelation::TYPE_DUPLICATES)
1227 # 2 is a dup of 1 but 1 is not a duplicate of 2
1236 # 2 is a dup of 1 but 1 is not a duplicate of 2
1228 assert !issue2.reload.duplicates.include?(issue1)
1237 assert !issue2.reload.duplicates.include?(issue1)
1229
1238
1230 # Closing issue 2
1239 # Closing issue 2
1231 issue2.init_journal(User.first, "Closing issue2")
1240 issue2.init_journal(User.first, "Closing issue2")
1232 issue2.status = IssueStatus.where(:is_closed => true).first
1241 issue2.status = IssueStatus.where(:is_closed => true).first
1233 assert issue2.save
1242 assert issue2.save
1234 # 1 should not be also closed
1243 # 1 should not be also closed
1235 assert !issue1.reload.closed?
1244 assert !issue1.reload.closed?
1236 end
1245 end
1237
1246
1238 def test_assignable_versions
1247 def test_assignable_versions
1239 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1248 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1240 :status_id => 1, :fixed_version_id => 1,
1249 :status_id => 1, :fixed_version_id => 1,
1241 :subject => 'New issue')
1250 :subject => 'New issue')
1242 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1251 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1243 end
1252 end
1244
1253
1245 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1254 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1246 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1255 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1247 :status_id => 1, :fixed_version_id => 1,
1256 :status_id => 1, :fixed_version_id => 1,
1248 :subject => 'New issue')
1257 :subject => 'New issue')
1249 assert !issue.save
1258 assert !issue.save
1250 assert_not_equal [], issue.errors[:fixed_version_id]
1259 assert_not_equal [], issue.errors[:fixed_version_id]
1251 end
1260 end
1252
1261
1253 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1262 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1254 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1263 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1255 :status_id => 1, :fixed_version_id => 2,
1264 :status_id => 1, :fixed_version_id => 2,
1256 :subject => 'New issue')
1265 :subject => 'New issue')
1257 assert !issue.save
1266 assert !issue.save
1258 assert_not_equal [], issue.errors[:fixed_version_id]
1267 assert_not_equal [], issue.errors[:fixed_version_id]
1259 end
1268 end
1260
1269
1261 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1270 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1262 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1271 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1263 :status_id => 1, :fixed_version_id => 3,
1272 :status_id => 1, :fixed_version_id => 3,
1264 :subject => 'New issue')
1273 :subject => 'New issue')
1265 assert issue.save
1274 assert issue.save
1266 end
1275 end
1267
1276
1268 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1277 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1269 issue = Issue.find(11)
1278 issue = Issue.find(11)
1270 assert_equal 'closed', issue.fixed_version.status
1279 assert_equal 'closed', issue.fixed_version.status
1271 issue.subject = 'Subject changed'
1280 issue.subject = 'Subject changed'
1272 assert issue.save
1281 assert issue.save
1273 end
1282 end
1274
1283
1275 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1284 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1276 issue = Issue.find(11)
1285 issue = Issue.find(11)
1277 issue.status_id = 1
1286 issue.status_id = 1
1278 assert !issue.save
1287 assert !issue.save
1279 assert_not_equal [], issue.errors[:base]
1288 assert_not_equal [], issue.errors[:base]
1280 end
1289 end
1281
1290
1282 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1291 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1283 issue = Issue.find(11)
1292 issue = Issue.find(11)
1284 issue.status_id = 1
1293 issue.status_id = 1
1285 issue.fixed_version_id = 3
1294 issue.fixed_version_id = 3
1286 assert issue.save
1295 assert issue.save
1287 end
1296 end
1288
1297
1289 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1298 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1290 issue = Issue.find(12)
1299 issue = Issue.find(12)
1291 assert_equal 'locked', issue.fixed_version.status
1300 assert_equal 'locked', issue.fixed_version.status
1292 issue.status_id = 1
1301 issue.status_id = 1
1293 assert issue.save
1302 assert issue.save
1294 end
1303 end
1295
1304
1296 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1305 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1297 issue = Issue.find(2)
1306 issue = Issue.find(2)
1298 assert_equal 2, issue.fixed_version_id
1307 assert_equal 2, issue.fixed_version_id
1299 issue.project_id = 3
1308 issue.project_id = 3
1300 assert_nil issue.fixed_version_id
1309 assert_nil issue.fixed_version_id
1301 issue.fixed_version_id = 2
1310 issue.fixed_version_id = 2
1302 assert !issue.save
1311 assert !issue.save
1303 assert_include 'Target version is not included in the list', issue.errors.full_messages
1312 assert_include 'Target version is not included in the list', issue.errors.full_messages
1304 end
1313 end
1305
1314
1306 def test_should_keep_shared_version_when_changing_project
1315 def test_should_keep_shared_version_when_changing_project
1307 Version.find(2).update_attribute :sharing, 'tree'
1316 Version.find(2).update_attribute :sharing, 'tree'
1308
1317
1309 issue = Issue.find(2)
1318 issue = Issue.find(2)
1310 assert_equal 2, issue.fixed_version_id
1319 assert_equal 2, issue.fixed_version_id
1311 issue.project_id = 3
1320 issue.project_id = 3
1312 assert_equal 2, issue.fixed_version_id
1321 assert_equal 2, issue.fixed_version_id
1313 assert issue.save
1322 assert issue.save
1314 end
1323 end
1315
1324
1316 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1325 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1317 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1326 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1318 end
1327 end
1319
1328
1320 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1329 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1321 Project.find(2).disable_module! :issue_tracking
1330 Project.find(2).disable_module! :issue_tracking
1322 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1331 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1323 end
1332 end
1324
1333
1325 def test_move_to_another_project_with_same_category
1334 def test_move_to_another_project_with_same_category
1326 issue = Issue.find(1)
1335 issue = Issue.find(1)
1327 issue.project = Project.find(2)
1336 issue.project = Project.find(2)
1328 assert issue.save
1337 assert issue.save
1329 issue.reload
1338 issue.reload
1330 assert_equal 2, issue.project_id
1339 assert_equal 2, issue.project_id
1331 # Category changes
1340 # Category changes
1332 assert_equal 4, issue.category_id
1341 assert_equal 4, issue.category_id
1333 # Make sure time entries were move to the target project
1342 # Make sure time entries were move to the target project
1334 assert_equal 2, issue.time_entries.first.project_id
1343 assert_equal 2, issue.time_entries.first.project_id
1335 end
1344 end
1336
1345
1337 def test_move_to_another_project_without_same_category
1346 def test_move_to_another_project_without_same_category
1338 issue = Issue.find(2)
1347 issue = Issue.find(2)
1339 issue.project = Project.find(2)
1348 issue.project = Project.find(2)
1340 assert issue.save
1349 assert issue.save
1341 issue.reload
1350 issue.reload
1342 assert_equal 2, issue.project_id
1351 assert_equal 2, issue.project_id
1343 # Category cleared
1352 # Category cleared
1344 assert_nil issue.category_id
1353 assert_nil issue.category_id
1345 end
1354 end
1346
1355
1347 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1356 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1348 issue = Issue.find(1)
1357 issue = Issue.find(1)
1349 issue.update_attribute(:fixed_version_id, 1)
1358 issue.update_attribute(:fixed_version_id, 1)
1350 issue.project = Project.find(2)
1359 issue.project = Project.find(2)
1351 assert issue.save
1360 assert issue.save
1352 issue.reload
1361 issue.reload
1353 assert_equal 2, issue.project_id
1362 assert_equal 2, issue.project_id
1354 # Cleared fixed_version
1363 # Cleared fixed_version
1355 assert_equal nil, issue.fixed_version
1364 assert_equal nil, issue.fixed_version
1356 end
1365 end
1357
1366
1358 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1367 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1359 issue = Issue.find(1)
1368 issue = Issue.find(1)
1360 issue.update_attribute(:fixed_version_id, 4)
1369 issue.update_attribute(:fixed_version_id, 4)
1361 issue.project = Project.find(5)
1370 issue.project = Project.find(5)
1362 assert issue.save
1371 assert issue.save
1363 issue.reload
1372 issue.reload
1364 assert_equal 5, issue.project_id
1373 assert_equal 5, issue.project_id
1365 # Keep fixed_version
1374 # Keep fixed_version
1366 assert_equal 4, issue.fixed_version_id
1375 assert_equal 4, issue.fixed_version_id
1367 end
1376 end
1368
1377
1369 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1378 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1370 issue = Issue.find(1)
1379 issue = Issue.find(1)
1371 issue.update_attribute(:fixed_version_id, 1)
1380 issue.update_attribute(:fixed_version_id, 1)
1372 issue.project = Project.find(5)
1381 issue.project = Project.find(5)
1373 assert issue.save
1382 assert issue.save
1374 issue.reload
1383 issue.reload
1375 assert_equal 5, issue.project_id
1384 assert_equal 5, issue.project_id
1376 # Cleared fixed_version
1385 # Cleared fixed_version
1377 assert_equal nil, issue.fixed_version
1386 assert_equal nil, issue.fixed_version
1378 end
1387 end
1379
1388
1380 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1389 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1381 issue = Issue.find(1)
1390 issue = Issue.find(1)
1382 issue.update_attribute(:fixed_version_id, 7)
1391 issue.update_attribute(:fixed_version_id, 7)
1383 issue.project = Project.find(2)
1392 issue.project = Project.find(2)
1384 assert issue.save
1393 assert issue.save
1385 issue.reload
1394 issue.reload
1386 assert_equal 2, issue.project_id
1395 assert_equal 2, issue.project_id
1387 # Keep fixed_version
1396 # Keep fixed_version
1388 assert_equal 7, issue.fixed_version_id
1397 assert_equal 7, issue.fixed_version_id
1389 end
1398 end
1390
1399
1391 def test_move_to_another_project_should_keep_parent_if_valid
1400 def test_move_to_another_project_should_keep_parent_if_valid
1392 issue = Issue.find(1)
1401 issue = Issue.find(1)
1393 issue.update_attribute(:parent_issue_id, 2)
1402 issue.update_attribute(:parent_issue_id, 2)
1394 issue.project = Project.find(3)
1403 issue.project = Project.find(3)
1395 assert issue.save
1404 assert issue.save
1396 issue.reload
1405 issue.reload
1397 assert_equal 2, issue.parent_id
1406 assert_equal 2, issue.parent_id
1398 end
1407 end
1399
1408
1400 def test_move_to_another_project_should_clear_parent_if_not_valid
1409 def test_move_to_another_project_should_clear_parent_if_not_valid
1401 issue = Issue.find(1)
1410 issue = Issue.find(1)
1402 issue.update_attribute(:parent_issue_id, 2)
1411 issue.update_attribute(:parent_issue_id, 2)
1403 issue.project = Project.find(2)
1412 issue.project = Project.find(2)
1404 assert issue.save
1413 assert issue.save
1405 issue.reload
1414 issue.reload
1406 assert_nil issue.parent_id
1415 assert_nil issue.parent_id
1407 end
1416 end
1408
1417
1409 def test_move_to_another_project_with_disabled_tracker
1418 def test_move_to_another_project_with_disabled_tracker
1410 issue = Issue.find(1)
1419 issue = Issue.find(1)
1411 target = Project.find(2)
1420 target = Project.find(2)
1412 target.tracker_ids = [3]
1421 target.tracker_ids = [3]
1413 target.save
1422 target.save
1414 issue.project = target
1423 issue.project = target
1415 assert issue.save
1424 assert issue.save
1416 issue.reload
1425 issue.reload
1417 assert_equal 2, issue.project_id
1426 assert_equal 2, issue.project_id
1418 assert_equal 3, issue.tracker_id
1427 assert_equal 3, issue.tracker_id
1419 end
1428 end
1420
1429
1421 def test_copy_to_the_same_project
1430 def test_copy_to_the_same_project
1422 issue = Issue.find(1)
1431 issue = Issue.find(1)
1423 copy = issue.copy
1432 copy = issue.copy
1424 assert_difference 'Issue.count' do
1433 assert_difference 'Issue.count' do
1425 copy.save!
1434 copy.save!
1426 end
1435 end
1427 assert_kind_of Issue, copy
1436 assert_kind_of Issue, copy
1428 assert_equal issue.project, copy.project
1437 assert_equal issue.project, copy.project
1429 assert_equal "125", copy.custom_value_for(2).value
1438 assert_equal "125", copy.custom_value_for(2).value
1430 end
1439 end
1431
1440
1432 def test_copy_to_another_project_and_tracker
1441 def test_copy_to_another_project_and_tracker
1433 issue = Issue.find(1)
1442 issue = Issue.find(1)
1434 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1443 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1435 assert_difference 'Issue.count' do
1444 assert_difference 'Issue.count' do
1436 copy.save!
1445 copy.save!
1437 end
1446 end
1438 copy.reload
1447 copy.reload
1439 assert_kind_of Issue, copy
1448 assert_kind_of Issue, copy
1440 assert_equal Project.find(3), copy.project
1449 assert_equal Project.find(3), copy.project
1441 assert_equal Tracker.find(2), copy.tracker
1450 assert_equal Tracker.find(2), copy.tracker
1442 # Custom field #2 is not associated with target tracker
1451 # Custom field #2 is not associated with target tracker
1443 assert_nil copy.custom_value_for(2)
1452 assert_nil copy.custom_value_for(2)
1444 end
1453 end
1445
1454
1446 test "#copy should not create a journal" do
1455 test "#copy should not create a journal" do
1447 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :assigned_to_id => 3}, :link => false)
1456 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :assigned_to_id => 3}, :link => false)
1448 copy.save!
1457 copy.save!
1449 assert_equal 0, copy.reload.journals.size
1458 assert_equal 0, copy.reload.journals.size
1450 end
1459 end
1451
1460
1452 test "#copy should allow assigned_to changes" do
1461 test "#copy should allow assigned_to changes" do
1453 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1462 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1454 assert_equal 3, copy.assigned_to_id
1463 assert_equal 3, copy.assigned_to_id
1455 end
1464 end
1456
1465
1457 test "#copy should allow status changes" do
1466 test "#copy should allow status changes" do
1458 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1467 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1459 assert_equal 2, copy.status_id
1468 assert_equal 2, copy.status_id
1460 end
1469 end
1461
1470
1462 test "#copy should allow start date changes" do
1471 test "#copy should allow start date changes" do
1463 date = Date.today
1472 date = Date.today
1464 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1473 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1465 assert_equal date, copy.start_date
1474 assert_equal date, copy.start_date
1466 end
1475 end
1467
1476
1468 test "#copy should allow due date changes" do
1477 test "#copy should allow due date changes" do
1469 date = Date.today
1478 date = Date.today
1470 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1479 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1471 assert_equal date, copy.due_date
1480 assert_equal date, copy.due_date
1472 end
1481 end
1473
1482
1474 test "#copy should set current user as author" do
1483 test "#copy should set current user as author" do
1475 User.current = User.find(9)
1484 User.current = User.find(9)
1476 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1485 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1477 assert_equal User.current, copy.author
1486 assert_equal User.current, copy.author
1478 end
1487 end
1479
1488
1480 test "#copy should create a journal with notes" do
1489 test "#copy should create a journal with notes" do
1481 date = Date.today
1490 date = Date.today
1482 notes = "Notes added when copying"
1491 notes = "Notes added when copying"
1483 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1492 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1484 copy.init_journal(User.current, notes)
1493 copy.init_journal(User.current, notes)
1485 copy.save!
1494 copy.save!
1486
1495
1487 assert_equal 1, copy.journals.size
1496 assert_equal 1, copy.journals.size
1488 journal = copy.journals.first
1497 journal = copy.journals.first
1489 assert_equal 0, journal.details.size
1498 assert_equal 0, journal.details.size
1490 assert_equal notes, journal.notes
1499 assert_equal notes, journal.notes
1491 end
1500 end
1492
1501
1493 def test_valid_parent_project
1502 def test_valid_parent_project
1494 issue = Issue.find(1)
1503 issue = Issue.find(1)
1495 issue_in_same_project = Issue.find(2)
1504 issue_in_same_project = Issue.find(2)
1496 issue_in_child_project = Issue.find(5)
1505 issue_in_child_project = Issue.find(5)
1497 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1506 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1498 issue_in_other_child_project = Issue.find(6)
1507 issue_in_other_child_project = Issue.find(6)
1499 issue_in_different_tree = Issue.find(4)
1508 issue_in_different_tree = Issue.find(4)
1500
1509
1501 with_settings :cross_project_subtasks => '' do
1510 with_settings :cross_project_subtasks => '' do
1502 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1511 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1503 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1512 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1504 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1513 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1505 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1514 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1506 end
1515 end
1507
1516
1508 with_settings :cross_project_subtasks => 'system' do
1517 with_settings :cross_project_subtasks => 'system' do
1509 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1518 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1510 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1519 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1511 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1520 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1512 end
1521 end
1513
1522
1514 with_settings :cross_project_subtasks => 'tree' do
1523 with_settings :cross_project_subtasks => 'tree' do
1515 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1524 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1516 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1525 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1517 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1526 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1518 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1527 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1519
1528
1520 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1529 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1521 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1530 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1522 end
1531 end
1523
1532
1524 with_settings :cross_project_subtasks => 'descendants' do
1533 with_settings :cross_project_subtasks => 'descendants' do
1525 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1534 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1526 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1535 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1527 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1536 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1528 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1537 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1529
1538
1530 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1539 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1531 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1540 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1532 end
1541 end
1533 end
1542 end
1534
1543
1535 def test_recipients_should_include_previous_assignee
1544 def test_recipients_should_include_previous_assignee
1536 user = User.find(3)
1545 user = User.find(3)
1537 user.members.update_all ["mail_notification = ?", false]
1546 user.members.update_all ["mail_notification = ?", false]
1538 user.update_attribute :mail_notification, 'only_assigned'
1547 user.update_attribute :mail_notification, 'only_assigned'
1539
1548
1540 issue = Issue.find(2)
1549 issue = Issue.find(2)
1541 issue.assigned_to = nil
1550 issue.assigned_to = nil
1542 assert_include user.mail, issue.recipients
1551 assert_include user.mail, issue.recipients
1543 issue.save!
1552 issue.save!
1544 assert !issue.recipients.include?(user.mail)
1553 assert !issue.recipients.include?(user.mail)
1545 end
1554 end
1546
1555
1547 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1556 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1548 issue = Issue.find(12)
1557 issue = Issue.find(12)
1549 assert issue.recipients.include?(issue.author.mail)
1558 assert issue.recipients.include?(issue.author.mail)
1550 # copy the issue to a private project
1559 # copy the issue to a private project
1551 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1560 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1552 # author is not a member of project anymore
1561 # author is not a member of project anymore
1553 assert !copy.recipients.include?(copy.author.mail)
1562 assert !copy.recipients.include?(copy.author.mail)
1554 end
1563 end
1555
1564
1556 def test_recipients_should_include_the_assigned_group_members
1565 def test_recipients_should_include_the_assigned_group_members
1557 group_member = User.generate!
1566 group_member = User.generate!
1558 group = Group.generate!
1567 group = Group.generate!
1559 group.users << group_member
1568 group.users << group_member
1560
1569
1561 issue = Issue.find(12)
1570 issue = Issue.find(12)
1562 issue.assigned_to = group
1571 issue.assigned_to = group
1563 assert issue.recipients.include?(group_member.mail)
1572 assert issue.recipients.include?(group_member.mail)
1564 end
1573 end
1565
1574
1566 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1575 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1567 user = User.find(3)
1576 user = User.find(3)
1568 issue = Issue.find(9)
1577 issue = Issue.find(9)
1569 Watcher.create!(:user => user, :watchable => issue)
1578 Watcher.create!(:user => user, :watchable => issue)
1570 assert issue.watched_by?(user)
1579 assert issue.watched_by?(user)
1571 assert !issue.watcher_recipients.include?(user.mail)
1580 assert !issue.watcher_recipients.include?(user.mail)
1572 end
1581 end
1573
1582
1574 def test_issue_destroy
1583 def test_issue_destroy
1575 Issue.find(1).destroy
1584 Issue.find(1).destroy
1576 assert_nil Issue.find_by_id(1)
1585 assert_nil Issue.find_by_id(1)
1577 assert_nil TimeEntry.find_by_issue_id(1)
1586 assert_nil TimeEntry.find_by_issue_id(1)
1578 end
1587 end
1579
1588
1580 def test_destroy_should_delete_time_entries_custom_values
1589 def test_destroy_should_delete_time_entries_custom_values
1581 issue = Issue.generate!
1590 issue = Issue.generate!
1582 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1591 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1583
1592
1584 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1593 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1585 assert issue.destroy
1594 assert issue.destroy
1586 end
1595 end
1587 end
1596 end
1588
1597
1589 def test_destroying_a_deleted_issue_should_not_raise_an_error
1598 def test_destroying_a_deleted_issue_should_not_raise_an_error
1590 issue = Issue.find(1)
1599 issue = Issue.find(1)
1591 Issue.find(1).destroy
1600 Issue.find(1).destroy
1592
1601
1593 assert_nothing_raised do
1602 assert_nothing_raised do
1594 assert_no_difference 'Issue.count' do
1603 assert_no_difference 'Issue.count' do
1595 issue.destroy
1604 issue.destroy
1596 end
1605 end
1597 assert issue.destroyed?
1606 assert issue.destroyed?
1598 end
1607 end
1599 end
1608 end
1600
1609
1601 def test_destroying_a_stale_issue_should_not_raise_an_error
1610 def test_destroying_a_stale_issue_should_not_raise_an_error
1602 issue = Issue.find(1)
1611 issue = Issue.find(1)
1603 Issue.find(1).update_attribute :subject, "Updated"
1612 Issue.find(1).update_attribute :subject, "Updated"
1604
1613
1605 assert_nothing_raised do
1614 assert_nothing_raised do
1606 assert_difference 'Issue.count', -1 do
1615 assert_difference 'Issue.count', -1 do
1607 issue.destroy
1616 issue.destroy
1608 end
1617 end
1609 assert issue.destroyed?
1618 assert issue.destroyed?
1610 end
1619 end
1611 end
1620 end
1612
1621
1613 def test_blocked
1622 def test_blocked
1614 blocked_issue = Issue.find(9)
1623 blocked_issue = Issue.find(9)
1615 blocking_issue = Issue.find(10)
1624 blocking_issue = Issue.find(10)
1616
1625
1617 assert blocked_issue.blocked?
1626 assert blocked_issue.blocked?
1618 assert !blocking_issue.blocked?
1627 assert !blocking_issue.blocked?
1619 end
1628 end
1620
1629
1621 def test_blocked_issues_dont_allow_closed_statuses
1630 def test_blocked_issues_dont_allow_closed_statuses
1622 blocked_issue = Issue.find(9)
1631 blocked_issue = Issue.find(9)
1623
1632
1624 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1633 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1625 assert !allowed_statuses.empty?
1634 assert !allowed_statuses.empty?
1626 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1635 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1627 assert closed_statuses.empty?
1636 assert closed_statuses.empty?
1628 end
1637 end
1629
1638
1630 def test_unblocked_issues_allow_closed_statuses
1639 def test_unblocked_issues_allow_closed_statuses
1631 blocking_issue = Issue.find(10)
1640 blocking_issue = Issue.find(10)
1632
1641
1633 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1642 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1634 assert !allowed_statuses.empty?
1643 assert !allowed_statuses.empty?
1635 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1644 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1636 assert !closed_statuses.empty?
1645 assert !closed_statuses.empty?
1637 end
1646 end
1638
1647
1639 def test_reschedule_an_issue_without_dates
1648 def test_reschedule_an_issue_without_dates
1640 with_settings :non_working_week_days => [] do
1649 with_settings :non_working_week_days => [] do
1641 issue = Issue.new(:start_date => nil, :due_date => nil)
1650 issue = Issue.new(:start_date => nil, :due_date => nil)
1642 issue.reschedule_on '2012-10-09'.to_date
1651 issue.reschedule_on '2012-10-09'.to_date
1643 assert_equal '2012-10-09'.to_date, issue.start_date
1652 assert_equal '2012-10-09'.to_date, issue.start_date
1644 assert_equal '2012-10-09'.to_date, issue.due_date
1653 assert_equal '2012-10-09'.to_date, issue.due_date
1645 end
1654 end
1646
1655
1647 with_settings :non_working_week_days => %w(6 7) do
1656 with_settings :non_working_week_days => %w(6 7) do
1648 issue = Issue.new(:start_date => nil, :due_date => nil)
1657 issue = Issue.new(:start_date => nil, :due_date => nil)
1649 issue.reschedule_on '2012-10-09'.to_date
1658 issue.reschedule_on '2012-10-09'.to_date
1650 assert_equal '2012-10-09'.to_date, issue.start_date
1659 assert_equal '2012-10-09'.to_date, issue.start_date
1651 assert_equal '2012-10-09'.to_date, issue.due_date
1660 assert_equal '2012-10-09'.to_date, issue.due_date
1652
1661
1653 issue = Issue.new(:start_date => nil, :due_date => nil)
1662 issue = Issue.new(:start_date => nil, :due_date => nil)
1654 issue.reschedule_on '2012-10-13'.to_date
1663 issue.reschedule_on '2012-10-13'.to_date
1655 assert_equal '2012-10-15'.to_date, issue.start_date
1664 assert_equal '2012-10-15'.to_date, issue.start_date
1656 assert_equal '2012-10-15'.to_date, issue.due_date
1665 assert_equal '2012-10-15'.to_date, issue.due_date
1657 end
1666 end
1658 end
1667 end
1659
1668
1660 def test_reschedule_an_issue_with_start_date
1669 def test_reschedule_an_issue_with_start_date
1661 with_settings :non_working_week_days => [] do
1670 with_settings :non_working_week_days => [] do
1662 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1671 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1663 issue.reschedule_on '2012-10-13'.to_date
1672 issue.reschedule_on '2012-10-13'.to_date
1664 assert_equal '2012-10-13'.to_date, issue.start_date
1673 assert_equal '2012-10-13'.to_date, issue.start_date
1665 assert_equal '2012-10-13'.to_date, issue.due_date
1674 assert_equal '2012-10-13'.to_date, issue.due_date
1666 end
1675 end
1667
1676
1668 with_settings :non_working_week_days => %w(6 7) do
1677 with_settings :non_working_week_days => %w(6 7) do
1669 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1678 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1670 issue.reschedule_on '2012-10-11'.to_date
1679 issue.reschedule_on '2012-10-11'.to_date
1671 assert_equal '2012-10-11'.to_date, issue.start_date
1680 assert_equal '2012-10-11'.to_date, issue.start_date
1672 assert_equal '2012-10-11'.to_date, issue.due_date
1681 assert_equal '2012-10-11'.to_date, issue.due_date
1673
1682
1674 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1683 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1675 issue.reschedule_on '2012-10-13'.to_date
1684 issue.reschedule_on '2012-10-13'.to_date
1676 assert_equal '2012-10-15'.to_date, issue.start_date
1685 assert_equal '2012-10-15'.to_date, issue.start_date
1677 assert_equal '2012-10-15'.to_date, issue.due_date
1686 assert_equal '2012-10-15'.to_date, issue.due_date
1678 end
1687 end
1679 end
1688 end
1680
1689
1681 def test_reschedule_an_issue_with_start_and_due_dates
1690 def test_reschedule_an_issue_with_start_and_due_dates
1682 with_settings :non_working_week_days => [] do
1691 with_settings :non_working_week_days => [] do
1683 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1692 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1684 issue.reschedule_on '2012-10-13'.to_date
1693 issue.reschedule_on '2012-10-13'.to_date
1685 assert_equal '2012-10-13'.to_date, issue.start_date
1694 assert_equal '2012-10-13'.to_date, issue.start_date
1686 assert_equal '2012-10-19'.to_date, issue.due_date
1695 assert_equal '2012-10-19'.to_date, issue.due_date
1687 end
1696 end
1688
1697
1689 with_settings :non_working_week_days => %w(6 7) do
1698 with_settings :non_working_week_days => %w(6 7) do
1690 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1699 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1691 issue.reschedule_on '2012-10-11'.to_date
1700 issue.reschedule_on '2012-10-11'.to_date
1692 assert_equal '2012-10-11'.to_date, issue.start_date
1701 assert_equal '2012-10-11'.to_date, issue.start_date
1693 assert_equal '2012-10-23'.to_date, issue.due_date
1702 assert_equal '2012-10-23'.to_date, issue.due_date
1694
1703
1695 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1704 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1696 issue.reschedule_on '2012-10-13'.to_date
1705 issue.reschedule_on '2012-10-13'.to_date
1697 assert_equal '2012-10-15'.to_date, issue.start_date
1706 assert_equal '2012-10-15'.to_date, issue.start_date
1698 assert_equal '2012-10-25'.to_date, issue.due_date
1707 assert_equal '2012-10-25'.to_date, issue.due_date
1699 end
1708 end
1700 end
1709 end
1701
1710
1702 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1711 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1703 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1712 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1704 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1713 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1705 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1714 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1706 :relation_type => IssueRelation::TYPE_PRECEDES)
1715 :relation_type => IssueRelation::TYPE_PRECEDES)
1707 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1716 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1708
1717
1709 issue1.reload
1718 issue1.reload
1710 issue1.due_date = '2012-10-23'
1719 issue1.due_date = '2012-10-23'
1711 issue1.save!
1720 issue1.save!
1712 issue2.reload
1721 issue2.reload
1713 assert_equal Date.parse('2012-10-24'), issue2.start_date
1722 assert_equal Date.parse('2012-10-24'), issue2.start_date
1714 assert_equal Date.parse('2012-10-26'), issue2.due_date
1723 assert_equal Date.parse('2012-10-26'), issue2.due_date
1715 end
1724 end
1716
1725
1717 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1726 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1718 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1727 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1719 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1728 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1720 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1729 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1721 :relation_type => IssueRelation::TYPE_PRECEDES)
1730 :relation_type => IssueRelation::TYPE_PRECEDES)
1722 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1731 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1723
1732
1724 issue1.reload
1733 issue1.reload
1725 issue1.start_date = '2012-09-17'
1734 issue1.start_date = '2012-09-17'
1726 issue1.due_date = '2012-09-18'
1735 issue1.due_date = '2012-09-18'
1727 issue1.save!
1736 issue1.save!
1728 issue2.reload
1737 issue2.reload
1729 assert_equal Date.parse('2012-09-19'), issue2.start_date
1738 assert_equal Date.parse('2012-09-19'), issue2.start_date
1730 assert_equal Date.parse('2012-09-21'), issue2.due_date
1739 assert_equal Date.parse('2012-09-21'), issue2.due_date
1731 end
1740 end
1732
1741
1733 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1742 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1734 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1743 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1735 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1744 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1736 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1745 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1737 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1746 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1738 :relation_type => IssueRelation::TYPE_PRECEDES)
1747 :relation_type => IssueRelation::TYPE_PRECEDES)
1739 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1748 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1740 :relation_type => IssueRelation::TYPE_PRECEDES)
1749 :relation_type => IssueRelation::TYPE_PRECEDES)
1741 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1750 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1742
1751
1743 issue1.reload
1752 issue1.reload
1744 issue1.start_date = '2012-09-17'
1753 issue1.start_date = '2012-09-17'
1745 issue1.due_date = '2012-09-18'
1754 issue1.due_date = '2012-09-18'
1746 issue1.save!
1755 issue1.save!
1747 issue2.reload
1756 issue2.reload
1748 # Issue 2 must start after Issue 3
1757 # Issue 2 must start after Issue 3
1749 assert_equal Date.parse('2012-10-03'), issue2.start_date
1758 assert_equal Date.parse('2012-10-03'), issue2.start_date
1750 assert_equal Date.parse('2012-10-05'), issue2.due_date
1759 assert_equal Date.parse('2012-10-05'), issue2.due_date
1751 end
1760 end
1752
1761
1753 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1762 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1754 with_settings :non_working_week_days => [] do
1763 with_settings :non_working_week_days => [] do
1755 stale = Issue.find(1)
1764 stale = Issue.find(1)
1756 issue = Issue.find(1)
1765 issue = Issue.find(1)
1757 issue.subject = "Updated"
1766 issue.subject = "Updated"
1758 issue.save!
1767 issue.save!
1759 date = 10.days.from_now.to_date
1768 date = 10.days.from_now.to_date
1760 assert_nothing_raised do
1769 assert_nothing_raised do
1761 stale.reschedule_on!(date)
1770 stale.reschedule_on!(date)
1762 end
1771 end
1763 assert_equal date, stale.reload.start_date
1772 assert_equal date, stale.reload.start_date
1764 end
1773 end
1765 end
1774 end
1766
1775
1767 def test_child_issue_should_consider_parent_soonest_start_on_create
1776 def test_child_issue_should_consider_parent_soonest_start_on_create
1768 set_language_if_valid 'en'
1777 set_language_if_valid 'en'
1769 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1778 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1770 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1779 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1771 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1780 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1772 :relation_type => IssueRelation::TYPE_PRECEDES)
1781 :relation_type => IssueRelation::TYPE_PRECEDES)
1773 issue1.reload
1782 issue1.reload
1774 issue2.reload
1783 issue2.reload
1775 assert_equal Date.parse('2012-10-18'), issue2.start_date
1784 assert_equal Date.parse('2012-10-18'), issue2.start_date
1776
1785
1777 with_settings :date_format => '%m/%d/%Y' do
1786 with_settings :date_format => '%m/%d/%Y' do
1778 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1787 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1779 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1788 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1780 assert !child.valid?
1789 assert !child.valid?
1781 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1790 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1782 assert_equal Date.parse('2012-10-18'), child.soonest_start
1791 assert_equal Date.parse('2012-10-18'), child.soonest_start
1783 child.start_date = '2012-10-18'
1792 child.start_date = '2012-10-18'
1784 assert child.save
1793 assert child.save
1785 end
1794 end
1786 end
1795 end
1787
1796
1788 def test_setting_parent_to_a_dependent_issue_should_not_validate
1797 def test_setting_parent_to_a_dependent_issue_should_not_validate
1789 set_language_if_valid 'en'
1798 set_language_if_valid 'en'
1790 issue1 = Issue.generate!
1799 issue1 = Issue.generate!
1791 issue2 = Issue.generate!
1800 issue2 = Issue.generate!
1792 issue3 = Issue.generate!
1801 issue3 = Issue.generate!
1793 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1802 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1794 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1803 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1795 issue3.reload
1804 issue3.reload
1796 issue3.parent_issue_id = issue2.id
1805 issue3.parent_issue_id = issue2.id
1797 assert !issue3.valid?
1806 assert !issue3.valid?
1798 assert_include 'Parent task is invalid', issue3.errors.full_messages
1807 assert_include 'Parent task is invalid', issue3.errors.full_messages
1799 end
1808 end
1800
1809
1801 def test_setting_parent_should_not_allow_circular_dependency
1810 def test_setting_parent_should_not_allow_circular_dependency
1802 set_language_if_valid 'en'
1811 set_language_if_valid 'en'
1803 issue1 = Issue.generate!
1812 issue1 = Issue.generate!
1804 issue2 = Issue.generate!
1813 issue2 = Issue.generate!
1805 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1814 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1806 issue3 = Issue.generate!
1815 issue3 = Issue.generate!
1807 issue2.reload
1816 issue2.reload
1808 issue2.parent_issue_id = issue3.id
1817 issue2.parent_issue_id = issue3.id
1809 issue2.save!
1818 issue2.save!
1810 issue4 = Issue.generate!
1819 issue4 = Issue.generate!
1811 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1820 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1812 issue4.reload
1821 issue4.reload
1813 issue4.parent_issue_id = issue1.id
1822 issue4.parent_issue_id = issue1.id
1814 assert !issue4.valid?
1823 assert !issue4.valid?
1815 assert_include 'Parent task is invalid', issue4.errors.full_messages
1824 assert_include 'Parent task is invalid', issue4.errors.full_messages
1816 end
1825 end
1817
1826
1818 def test_overdue
1827 def test_overdue
1819 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1828 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1820 assert !Issue.new(:due_date => Date.today).overdue?
1829 assert !Issue.new(:due_date => Date.today).overdue?
1821 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1830 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1822 assert !Issue.new(:due_date => nil).overdue?
1831 assert !Issue.new(:due_date => nil).overdue?
1823 assert !Issue.new(:due_date => 1.day.ago.to_date,
1832 assert !Issue.new(:due_date => 1.day.ago.to_date,
1824 :status => IssueStatus.where(:is_closed => true).first
1833 :status => IssueStatus.where(:is_closed => true).first
1825 ).overdue?
1834 ).overdue?
1826 end
1835 end
1827
1836
1828 test "#behind_schedule? should be false if the issue has no start_date" do
1837 test "#behind_schedule? should be false if the issue has no start_date" do
1829 assert !Issue.new(:start_date => nil,
1838 assert !Issue.new(:start_date => nil,
1830 :due_date => 1.day.from_now.to_date,
1839 :due_date => 1.day.from_now.to_date,
1831 :done_ratio => 0).behind_schedule?
1840 :done_ratio => 0).behind_schedule?
1832 end
1841 end
1833
1842
1834 test "#behind_schedule? should be false if the issue has no end_date" do
1843 test "#behind_schedule? should be false if the issue has no end_date" do
1835 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1844 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1836 :due_date => nil,
1845 :due_date => nil,
1837 :done_ratio => 0).behind_schedule?
1846 :done_ratio => 0).behind_schedule?
1838 end
1847 end
1839
1848
1840 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1849 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1841 assert !Issue.new(:start_date => 50.days.ago.to_date,
1850 assert !Issue.new(:start_date => 50.days.ago.to_date,
1842 :due_date => 50.days.from_now.to_date,
1851 :due_date => 50.days.from_now.to_date,
1843 :done_ratio => 90).behind_schedule?
1852 :done_ratio => 90).behind_schedule?
1844 end
1853 end
1845
1854
1846 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1855 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1847 assert Issue.new(:start_date => 1.day.ago.to_date,
1856 assert Issue.new(:start_date => 1.day.ago.to_date,
1848 :due_date => 1.day.from_now.to_date,
1857 :due_date => 1.day.from_now.to_date,
1849 :done_ratio => 0).behind_schedule?
1858 :done_ratio => 0).behind_schedule?
1850 end
1859 end
1851
1860
1852 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1861 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1853 assert Issue.new(:start_date => 100.days.ago.to_date,
1862 assert Issue.new(:start_date => 100.days.ago.to_date,
1854 :due_date => Date.today,
1863 :due_date => Date.today,
1855 :done_ratio => 90).behind_schedule?
1864 :done_ratio => 90).behind_schedule?
1856 end
1865 end
1857
1866
1858 test "#assignable_users should be Users" do
1867 test "#assignable_users should be Users" do
1859 assert_kind_of User, Issue.find(1).assignable_users.first
1868 assert_kind_of User, Issue.find(1).assignable_users.first
1860 end
1869 end
1861
1870
1862 test "#assignable_users should include the issue author" do
1871 test "#assignable_users should include the issue author" do
1863 non_project_member = User.generate!
1872 non_project_member = User.generate!
1864 issue = Issue.generate!(:author => non_project_member)
1873 issue = Issue.generate!(:author => non_project_member)
1865
1874
1866 assert issue.assignable_users.include?(non_project_member)
1875 assert issue.assignable_users.include?(non_project_member)
1867 end
1876 end
1868
1877
1869 test "#assignable_users should include the current assignee" do
1878 test "#assignable_users should include the current assignee" do
1870 user = User.generate!
1879 user = User.generate!
1871 issue = Issue.generate!(:assigned_to => user)
1880 issue = Issue.generate!(:assigned_to => user)
1872 user.lock!
1881 user.lock!
1873
1882
1874 assert Issue.find(issue.id).assignable_users.include?(user)
1883 assert Issue.find(issue.id).assignable_users.include?(user)
1875 end
1884 end
1876
1885
1877 test "#assignable_users should not show the issue author twice" do
1886 test "#assignable_users should not show the issue author twice" do
1878 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1887 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1879 assert_equal 2, assignable_user_ids.length
1888 assert_equal 2, assignable_user_ids.length
1880
1889
1881 assignable_user_ids.each do |user_id|
1890 assignable_user_ids.each do |user_id|
1882 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1891 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1883 "User #{user_id} appears more or less than once"
1892 "User #{user_id} appears more or less than once"
1884 end
1893 end
1885 end
1894 end
1886
1895
1887 test "#assignable_users with issue_group_assignment should include groups" do
1896 test "#assignable_users with issue_group_assignment should include groups" do
1888 issue = Issue.new(:project => Project.find(2))
1897 issue = Issue.new(:project => Project.find(2))
1889
1898
1890 with_settings :issue_group_assignment => '1' do
1899 with_settings :issue_group_assignment => '1' do
1891 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1900 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1892 assert issue.assignable_users.include?(Group.find(11))
1901 assert issue.assignable_users.include?(Group.find(11))
1893 end
1902 end
1894 end
1903 end
1895
1904
1896 test "#assignable_users without issue_group_assignment should not include groups" do
1905 test "#assignable_users without issue_group_assignment should not include groups" do
1897 issue = Issue.new(:project => Project.find(2))
1906 issue = Issue.new(:project => Project.find(2))
1898
1907
1899 with_settings :issue_group_assignment => '0' do
1908 with_settings :issue_group_assignment => '0' do
1900 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1909 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1901 assert !issue.assignable_users.include?(Group.find(11))
1910 assert !issue.assignable_users.include?(Group.find(11))
1902 end
1911 end
1903 end
1912 end
1904
1913
1905 def test_assignable_users_should_not_include_builtin_groups
1914 def test_assignable_users_should_not_include_builtin_groups
1906 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1915 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1907 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1916 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1908 issue = Issue.new(:project => Project.find(1))
1917 issue = Issue.new(:project => Project.find(1))
1909
1918
1910 with_settings :issue_group_assignment => '1' do
1919 with_settings :issue_group_assignment => '1' do
1911 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1920 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1912 end
1921 end
1913 end
1922 end
1914
1923
1915 def test_create_should_send_email_notification
1924 def test_create_should_send_email_notification
1916 ActionMailer::Base.deliveries.clear
1925 ActionMailer::Base.deliveries.clear
1917 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1926 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1918 :author_id => 3, :status_id => 1,
1927 :author_id => 3, :status_id => 1,
1919 :priority => IssuePriority.all.first,
1928 :priority => IssuePriority.all.first,
1920 :subject => 'test_create', :estimated_hours => '1:30')
1929 :subject => 'test_create', :estimated_hours => '1:30')
1921 with_settings :notified_events => %w(issue_added) do
1930 with_settings :notified_events => %w(issue_added) do
1922 assert issue.save
1931 assert issue.save
1923 assert_equal 1, ActionMailer::Base.deliveries.size
1932 assert_equal 1, ActionMailer::Base.deliveries.size
1924 end
1933 end
1925 end
1934 end
1926
1935
1927 def test_create_should_send_one_email_notification_with_both_settings
1936 def test_create_should_send_one_email_notification_with_both_settings
1928 ActionMailer::Base.deliveries.clear
1937 ActionMailer::Base.deliveries.clear
1929 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1938 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1930 :author_id => 3, :status_id => 1,
1939 :author_id => 3, :status_id => 1,
1931 :priority => IssuePriority.all.first,
1940 :priority => IssuePriority.all.first,
1932 :subject => 'test_create', :estimated_hours => '1:30')
1941 :subject => 'test_create', :estimated_hours => '1:30')
1933 with_settings :notified_events => %w(issue_added issue_updated) do
1942 with_settings :notified_events => %w(issue_added issue_updated) do
1934 assert issue.save
1943 assert issue.save
1935 assert_equal 1, ActionMailer::Base.deliveries.size
1944 assert_equal 1, ActionMailer::Base.deliveries.size
1936 end
1945 end
1937 end
1946 end
1938
1947
1939 def test_create_should_not_send_email_notification_with_no_setting
1948 def test_create_should_not_send_email_notification_with_no_setting
1940 ActionMailer::Base.deliveries.clear
1949 ActionMailer::Base.deliveries.clear
1941 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1950 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1942 :author_id => 3, :status_id => 1,
1951 :author_id => 3, :status_id => 1,
1943 :priority => IssuePriority.all.first,
1952 :priority => IssuePriority.all.first,
1944 :subject => 'test_create', :estimated_hours => '1:30')
1953 :subject => 'test_create', :estimated_hours => '1:30')
1945 with_settings :notified_events => [] do
1954 with_settings :notified_events => [] do
1946 assert issue.save
1955 assert issue.save
1947 assert_equal 0, ActionMailer::Base.deliveries.size
1956 assert_equal 0, ActionMailer::Base.deliveries.size
1948 end
1957 end
1949 end
1958 end
1950
1959
1951 def test_update_should_notify_previous_assignee
1960 def test_update_should_notify_previous_assignee
1952 ActionMailer::Base.deliveries.clear
1961 ActionMailer::Base.deliveries.clear
1953 user = User.find(3)
1962 user = User.find(3)
1954 user.members.update_all ["mail_notification = ?", false]
1963 user.members.update_all ["mail_notification = ?", false]
1955 user.update_attribute :mail_notification, 'only_assigned'
1964 user.update_attribute :mail_notification, 'only_assigned'
1956
1965
1957 with_settings :notified_events => %w(issue_updated) do
1966 with_settings :notified_events => %w(issue_updated) do
1958 issue = Issue.find(2)
1967 issue = Issue.find(2)
1959 issue.init_journal User.find(1)
1968 issue.init_journal User.find(1)
1960 issue.assigned_to = nil
1969 issue.assigned_to = nil
1961 issue.save!
1970 issue.save!
1962 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
1971 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
1963 end
1972 end
1964 end
1973 end
1965
1974
1966 def test_stale_issue_should_not_send_email_notification
1975 def test_stale_issue_should_not_send_email_notification
1967 ActionMailer::Base.deliveries.clear
1976 ActionMailer::Base.deliveries.clear
1968 issue = Issue.find(1)
1977 issue = Issue.find(1)
1969 stale = Issue.find(1)
1978 stale = Issue.find(1)
1970
1979
1971 issue.init_journal(User.find(1))
1980 issue.init_journal(User.find(1))
1972 issue.subject = 'Subjet update'
1981 issue.subject = 'Subjet update'
1973 with_settings :notified_events => %w(issue_updated) do
1982 with_settings :notified_events => %w(issue_updated) do
1974 assert issue.save
1983 assert issue.save
1975 assert_equal 1, ActionMailer::Base.deliveries.size
1984 assert_equal 1, ActionMailer::Base.deliveries.size
1976 ActionMailer::Base.deliveries.clear
1985 ActionMailer::Base.deliveries.clear
1977
1986
1978 stale.init_journal(User.find(1))
1987 stale.init_journal(User.find(1))
1979 stale.subject = 'Another subjet update'
1988 stale.subject = 'Another subjet update'
1980 assert_raise ActiveRecord::StaleObjectError do
1989 assert_raise ActiveRecord::StaleObjectError do
1981 stale.save
1990 stale.save
1982 end
1991 end
1983 assert ActionMailer::Base.deliveries.empty?
1992 assert ActionMailer::Base.deliveries.empty?
1984 end
1993 end
1985 end
1994 end
1986
1995
1987 def test_journalized_description
1996 def test_journalized_description
1988 IssueCustomField.delete_all
1997 IssueCustomField.delete_all
1989
1998
1990 i = Issue.first
1999 i = Issue.first
1991 old_description = i.description
2000 old_description = i.description
1992 new_description = "This is the new description"
2001 new_description = "This is the new description"
1993
2002
1994 i.init_journal(User.find(2))
2003 i.init_journal(User.find(2))
1995 i.description = new_description
2004 i.description = new_description
1996 assert_difference 'Journal.count', 1 do
2005 assert_difference 'Journal.count', 1 do
1997 assert_difference 'JournalDetail.count', 1 do
2006 assert_difference 'JournalDetail.count', 1 do
1998 i.save!
2007 i.save!
1999 end
2008 end
2000 end
2009 end
2001
2010
2002 detail = JournalDetail.order('id DESC').first
2011 detail = JournalDetail.order('id DESC').first
2003 assert_equal i, detail.journal.journalized
2012 assert_equal i, detail.journal.journalized
2004 assert_equal 'attr', detail.property
2013 assert_equal 'attr', detail.property
2005 assert_equal 'description', detail.prop_key
2014 assert_equal 'description', detail.prop_key
2006 assert_equal old_description, detail.old_value
2015 assert_equal old_description, detail.old_value
2007 assert_equal new_description, detail.value
2016 assert_equal new_description, detail.value
2008 end
2017 end
2009
2018
2010 def test_blank_descriptions_should_not_be_journalized
2019 def test_blank_descriptions_should_not_be_journalized
2011 IssueCustomField.delete_all
2020 IssueCustomField.delete_all
2012 Issue.where(:id => 1).update_all("description = NULL")
2021 Issue.where(:id => 1).update_all("description = NULL")
2013
2022
2014 i = Issue.find(1)
2023 i = Issue.find(1)
2015 i.init_journal(User.find(2))
2024 i.init_journal(User.find(2))
2016 i.subject = "blank description"
2025 i.subject = "blank description"
2017 i.description = "\r\n"
2026 i.description = "\r\n"
2018
2027
2019 assert_difference 'Journal.count', 1 do
2028 assert_difference 'Journal.count', 1 do
2020 assert_difference 'JournalDetail.count', 1 do
2029 assert_difference 'JournalDetail.count', 1 do
2021 i.save!
2030 i.save!
2022 end
2031 end
2023 end
2032 end
2024 end
2033 end
2025
2034
2026 def test_journalized_multi_custom_field
2035 def test_journalized_multi_custom_field
2027 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2036 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2028 :is_filter => true, :is_for_all => true,
2037 :is_filter => true, :is_for_all => true,
2029 :tracker_ids => [1],
2038 :tracker_ids => [1],
2030 :possible_values => ['value1', 'value2', 'value3'],
2039 :possible_values => ['value1', 'value2', 'value3'],
2031 :multiple => true)
2040 :multiple => true)
2032
2041
2033 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2042 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2034 :subject => 'Test', :author_id => 1)
2043 :subject => 'Test', :author_id => 1)
2035
2044
2036 assert_difference 'Journal.count' do
2045 assert_difference 'Journal.count' do
2037 assert_difference 'JournalDetail.count' do
2046 assert_difference 'JournalDetail.count' do
2038 issue.init_journal(User.first)
2047 issue.init_journal(User.first)
2039 issue.custom_field_values = {field.id => ['value1']}
2048 issue.custom_field_values = {field.id => ['value1']}
2040 issue.save!
2049 issue.save!
2041 end
2050 end
2042 assert_difference 'JournalDetail.count' do
2051 assert_difference 'JournalDetail.count' do
2043 issue.init_journal(User.first)
2052 issue.init_journal(User.first)
2044 issue.custom_field_values = {field.id => ['value1', 'value2']}
2053 issue.custom_field_values = {field.id => ['value1', 'value2']}
2045 issue.save!
2054 issue.save!
2046 end
2055 end
2047 assert_difference 'JournalDetail.count', 2 do
2056 assert_difference 'JournalDetail.count', 2 do
2048 issue.init_journal(User.first)
2057 issue.init_journal(User.first)
2049 issue.custom_field_values = {field.id => ['value3', 'value2']}
2058 issue.custom_field_values = {field.id => ['value3', 'value2']}
2050 issue.save!
2059 issue.save!
2051 end
2060 end
2052 assert_difference 'JournalDetail.count', 2 do
2061 assert_difference 'JournalDetail.count', 2 do
2053 issue.init_journal(User.first)
2062 issue.init_journal(User.first)
2054 issue.custom_field_values = {field.id => nil}
2063 issue.custom_field_values = {field.id => nil}
2055 issue.save!
2064 issue.save!
2056 end
2065 end
2057 end
2066 end
2058 end
2067 end
2059
2068
2060 def test_description_eol_should_be_normalized
2069 def test_description_eol_should_be_normalized
2061 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2070 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2062 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2071 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2063 end
2072 end
2064
2073
2065 def test_saving_twice_should_not_duplicate_journal_details
2074 def test_saving_twice_should_not_duplicate_journal_details
2066 i = Issue.first
2075 i = Issue.first
2067 i.init_journal(User.find(2), 'Some notes')
2076 i.init_journal(User.find(2), 'Some notes')
2068 # initial changes
2077 # initial changes
2069 i.subject = 'New subject'
2078 i.subject = 'New subject'
2070 i.done_ratio = i.done_ratio + 10
2079 i.done_ratio = i.done_ratio + 10
2071 assert_difference 'Journal.count' do
2080 assert_difference 'Journal.count' do
2072 assert i.save
2081 assert i.save
2073 end
2082 end
2074 # 1 more change
2083 # 1 more change
2075 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2084 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2076 assert_no_difference 'Journal.count' do
2085 assert_no_difference 'Journal.count' do
2077 assert_difference 'JournalDetail.count', 1 do
2086 assert_difference 'JournalDetail.count', 1 do
2078 i.save
2087 i.save
2079 end
2088 end
2080 end
2089 end
2081 # no more change
2090 # no more change
2082 assert_no_difference 'Journal.count' do
2091 assert_no_difference 'Journal.count' do
2083 assert_no_difference 'JournalDetail.count' do
2092 assert_no_difference 'JournalDetail.count' do
2084 i.save
2093 i.save
2085 end
2094 end
2086 end
2095 end
2087 end
2096 end
2088
2097
2089 def test_all_dependent_issues
2098 def test_all_dependent_issues
2090 IssueRelation.delete_all
2099 IssueRelation.delete_all
2091 assert IssueRelation.create!(:issue_from => Issue.find(1),
2100 assert IssueRelation.create!(:issue_from => Issue.find(1),
2092 :issue_to => Issue.find(2),
2101 :issue_to => Issue.find(2),
2093 :relation_type => IssueRelation::TYPE_PRECEDES)
2102 :relation_type => IssueRelation::TYPE_PRECEDES)
2094 assert IssueRelation.create!(:issue_from => Issue.find(2),
2103 assert IssueRelation.create!(:issue_from => Issue.find(2),
2095 :issue_to => Issue.find(3),
2104 :issue_to => Issue.find(3),
2096 :relation_type => IssueRelation::TYPE_PRECEDES)
2105 :relation_type => IssueRelation::TYPE_PRECEDES)
2097 assert IssueRelation.create!(:issue_from => Issue.find(3),
2106 assert IssueRelation.create!(:issue_from => Issue.find(3),
2098 :issue_to => Issue.find(8),
2107 :issue_to => Issue.find(8),
2099 :relation_type => IssueRelation::TYPE_PRECEDES)
2108 :relation_type => IssueRelation::TYPE_PRECEDES)
2100
2109
2101 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2110 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2102 end
2111 end
2103
2112
2104 def test_all_dependent_issues_with_subtask
2113 def test_all_dependent_issues_with_subtask
2105 IssueRelation.delete_all
2114 IssueRelation.delete_all
2106
2115
2107 project = Project.generate!(:name => "testproject")
2116 project = Project.generate!(:name => "testproject")
2108
2117
2109 parentIssue = Issue.generate!(:project => project)
2118 parentIssue = Issue.generate!(:project => project)
2110 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2119 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2111 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2120 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2112
2121
2113 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2122 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2114 end
2123 end
2115
2124
2116 def test_all_dependent_issues_does_not_include_self
2125 def test_all_dependent_issues_does_not_include_self
2117 IssueRelation.delete_all
2126 IssueRelation.delete_all
2118
2127
2119 project = Project.generate!(:name => "testproject")
2128 project = Project.generate!(:name => "testproject")
2120
2129
2121 parentIssue = Issue.generate!(:project => project)
2130 parentIssue = Issue.generate!(:project => project)
2122 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2131 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2123
2132
2124 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2133 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2125 end
2134 end
2126
2135
2127 def test_all_dependent_issues_with_parenttask_and_sibling
2136 def test_all_dependent_issues_with_parenttask_and_sibling
2128 IssueRelation.delete_all
2137 IssueRelation.delete_all
2129
2138
2130 project = Project.generate!(:name => "testproject")
2139 project = Project.generate!(:name => "testproject")
2131
2140
2132 parentIssue = Issue.generate!(:project => project)
2141 parentIssue = Issue.generate!(:project => project)
2133 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2142 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2134 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2143 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2135
2144
2136 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2145 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2137 end
2146 end
2138
2147
2139 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2148 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2140 IssueRelation.delete_all
2149 IssueRelation.delete_all
2141
2150
2142 project = Project.generate!(:name => "testproject")
2151 project = Project.generate!(:name => "testproject")
2143
2152
2144 parentIssue1 = Issue.generate!(:project => project)
2153 parentIssue1 = Issue.generate!(:project => project)
2145 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2154 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2146 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2155 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2147
2156
2148 parentIssue2 = Issue.generate!(:project => project)
2157 parentIssue2 = Issue.generate!(:project => project)
2149 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2158 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2150 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2159 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2151
2160
2152
2161
2153 assert IssueRelation.create(:issue_from => parentIssue1,
2162 assert IssueRelation.create(:issue_from => parentIssue1,
2154 :issue_to => childIssue2_2,
2163 :issue_to => childIssue2_2,
2155 :relation_type => IssueRelation::TYPE_BLOCKS)
2164 :relation_type => IssueRelation::TYPE_BLOCKS)
2156
2165
2157 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2166 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2158 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2167 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2159 end
2168 end
2160
2169
2161 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2170 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2162 IssueRelation.delete_all
2171 IssueRelation.delete_all
2163
2172
2164 project = Project.generate!(:name => "testproject")
2173 project = Project.generate!(:name => "testproject")
2165
2174
2166 parentIssue1 = Issue.generate!(:project => project)
2175 parentIssue1 = Issue.generate!(:project => project)
2167 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2176 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2168 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2177 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2169
2178
2170 parentIssue2 = Issue.generate!(:project => project)
2179 parentIssue2 = Issue.generate!(:project => project)
2171 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2180 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2172 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2181 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2173
2182
2174
2183
2175 assert IssueRelation.create(:issue_from => parentIssue1,
2184 assert IssueRelation.create(:issue_from => parentIssue1,
2176 :issue_to => parentIssue2,
2185 :issue_to => parentIssue2,
2177 :relation_type => IssueRelation::TYPE_BLOCKS)
2186 :relation_type => IssueRelation::TYPE_BLOCKS)
2178
2187
2179 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2188 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2180 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2189 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2181 end
2190 end
2182
2191
2183 def test_all_dependent_issues_with_transitive_relation
2192 def test_all_dependent_issues_with_transitive_relation
2184 IssueRelation.delete_all
2193 IssueRelation.delete_all
2185
2194
2186 project = Project.generate!(:name => "testproject")
2195 project = Project.generate!(:name => "testproject")
2187
2196
2188 parentIssue1 = Issue.generate!(:project => project)
2197 parentIssue1 = Issue.generate!(:project => project)
2189 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2198 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2190
2199
2191 parentIssue2 = Issue.generate!(:project => project)
2200 parentIssue2 = Issue.generate!(:project => project)
2192 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2201 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2193
2202
2194 independentIssue = Issue.generate!(:project => project)
2203 independentIssue = Issue.generate!(:project => project)
2195
2204
2196 assert IssueRelation.create(:issue_from => parentIssue1,
2205 assert IssueRelation.create(:issue_from => parentIssue1,
2197 :issue_to => childIssue2_1,
2206 :issue_to => childIssue2_1,
2198 :relation_type => IssueRelation::TYPE_RELATES)
2207 :relation_type => IssueRelation::TYPE_RELATES)
2199
2208
2200 assert IssueRelation.create(:issue_from => childIssue2_1,
2209 assert IssueRelation.create(:issue_from => childIssue2_1,
2201 :issue_to => independentIssue,
2210 :issue_to => independentIssue,
2202 :relation_type => IssueRelation::TYPE_RELATES)
2211 :relation_type => IssueRelation::TYPE_RELATES)
2203
2212
2204 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2213 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2205 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2214 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2206 end
2215 end
2207
2216
2208 def test_all_dependent_issues_with_transitive_relation2
2217 def test_all_dependent_issues_with_transitive_relation2
2209 IssueRelation.delete_all
2218 IssueRelation.delete_all
2210
2219
2211 project = Project.generate!(:name => "testproject")
2220 project = Project.generate!(:name => "testproject")
2212
2221
2213 parentIssue1 = Issue.generate!(:project => project)
2222 parentIssue1 = Issue.generate!(:project => project)
2214 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2223 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2215
2224
2216 parentIssue2 = Issue.generate!(:project => project)
2225 parentIssue2 = Issue.generate!(:project => project)
2217 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2226 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2218
2227
2219 independentIssue = Issue.generate!(:project => project)
2228 independentIssue = Issue.generate!(:project => project)
2220
2229
2221 assert IssueRelation.create(:issue_from => parentIssue1,
2230 assert IssueRelation.create(:issue_from => parentIssue1,
2222 :issue_to => independentIssue,
2231 :issue_to => independentIssue,
2223 :relation_type => IssueRelation::TYPE_RELATES)
2232 :relation_type => IssueRelation::TYPE_RELATES)
2224
2233
2225 assert IssueRelation.create(:issue_from => independentIssue,
2234 assert IssueRelation.create(:issue_from => independentIssue,
2226 :issue_to => childIssue2_1,
2235 :issue_to => childIssue2_1,
2227 :relation_type => IssueRelation::TYPE_RELATES)
2236 :relation_type => IssueRelation::TYPE_RELATES)
2228
2237
2229 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2238 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2230 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2239 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2231
2240
2232 end
2241 end
2233
2242
2234 def test_all_dependent_issues_with_persistent_circular_dependency
2243 def test_all_dependent_issues_with_persistent_circular_dependency
2235 IssueRelation.delete_all
2244 IssueRelation.delete_all
2236 assert IssueRelation.create!(:issue_from => Issue.find(1),
2245 assert IssueRelation.create!(:issue_from => Issue.find(1),
2237 :issue_to => Issue.find(2),
2246 :issue_to => Issue.find(2),
2238 :relation_type => IssueRelation::TYPE_PRECEDES)
2247 :relation_type => IssueRelation::TYPE_PRECEDES)
2239 assert IssueRelation.create!(:issue_from => Issue.find(2),
2248 assert IssueRelation.create!(:issue_from => Issue.find(2),
2240 :issue_to => Issue.find(3),
2249 :issue_to => Issue.find(3),
2241 :relation_type => IssueRelation::TYPE_PRECEDES)
2250 :relation_type => IssueRelation::TYPE_PRECEDES)
2242
2251
2243 r = IssueRelation.create!(:issue_from => Issue.find(3),
2252 r = IssueRelation.create!(:issue_from => Issue.find(3),
2244 :issue_to => Issue.find(7),
2253 :issue_to => Issue.find(7),
2245 :relation_type => IssueRelation::TYPE_PRECEDES)
2254 :relation_type => IssueRelation::TYPE_PRECEDES)
2246 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2255 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2247
2256
2248 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2257 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2249 end
2258 end
2250
2259
2251 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2260 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2252 IssueRelation.delete_all
2261 IssueRelation.delete_all
2253 assert IssueRelation.create!(:issue_from => Issue.find(1),
2262 assert IssueRelation.create!(:issue_from => Issue.find(1),
2254 :issue_to => Issue.find(2),
2263 :issue_to => Issue.find(2),
2255 :relation_type => IssueRelation::TYPE_RELATES)
2264 :relation_type => IssueRelation::TYPE_RELATES)
2256 assert IssueRelation.create!(:issue_from => Issue.find(2),
2265 assert IssueRelation.create!(:issue_from => Issue.find(2),
2257 :issue_to => Issue.find(3),
2266 :issue_to => Issue.find(3),
2258 :relation_type => IssueRelation::TYPE_RELATES)
2267 :relation_type => IssueRelation::TYPE_RELATES)
2259 assert IssueRelation.create!(:issue_from => Issue.find(3),
2268 assert IssueRelation.create!(:issue_from => Issue.find(3),
2260 :issue_to => Issue.find(8),
2269 :issue_to => Issue.find(8),
2261 :relation_type => IssueRelation::TYPE_RELATES)
2270 :relation_type => IssueRelation::TYPE_RELATES)
2262
2271
2263 r = IssueRelation.create!(:issue_from => Issue.find(8),
2272 r = IssueRelation.create!(:issue_from => Issue.find(8),
2264 :issue_to => Issue.find(7),
2273 :issue_to => Issue.find(7),
2265 :relation_type => IssueRelation::TYPE_RELATES)
2274 :relation_type => IssueRelation::TYPE_RELATES)
2266 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2275 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2267
2276
2268 r = IssueRelation.create!(:issue_from => Issue.find(3),
2277 r = IssueRelation.create!(:issue_from => Issue.find(3),
2269 :issue_to => Issue.find(7),
2278 :issue_to => Issue.find(7),
2270 :relation_type => IssueRelation::TYPE_RELATES)
2279 :relation_type => IssueRelation::TYPE_RELATES)
2271 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2280 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2272
2281
2273 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2282 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2274 end
2283 end
2275
2284
2276 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2285 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2277 @issue = Issue.find(1)
2286 @issue = Issue.find(1)
2278 @issue_status = IssueStatus.find(1)
2287 @issue_status = IssueStatus.find(1)
2279 @issue_status.update_attribute(:default_done_ratio, 50)
2288 @issue_status.update_attribute(:default_done_ratio, 50)
2280 @issue2 = Issue.find(2)
2289 @issue2 = Issue.find(2)
2281 @issue_status2 = IssueStatus.find(2)
2290 @issue_status2 = IssueStatus.find(2)
2282 @issue_status2.update_attribute(:default_done_ratio, 0)
2291 @issue_status2.update_attribute(:default_done_ratio, 0)
2283
2292
2284 with_settings :issue_done_ratio => 'issue_field' do
2293 with_settings :issue_done_ratio => 'issue_field' do
2285 assert_equal 0, @issue.done_ratio
2294 assert_equal 0, @issue.done_ratio
2286 assert_equal 30, @issue2.done_ratio
2295 assert_equal 30, @issue2.done_ratio
2287 end
2296 end
2288
2297
2289 with_settings :issue_done_ratio => 'issue_status' do
2298 with_settings :issue_done_ratio => 'issue_status' do
2290 assert_equal 50, @issue.done_ratio
2299 assert_equal 50, @issue.done_ratio
2291 assert_equal 0, @issue2.done_ratio
2300 assert_equal 0, @issue2.done_ratio
2292 end
2301 end
2293 end
2302 end
2294
2303
2295 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2304 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2296 @issue = Issue.find(1)
2305 @issue = Issue.find(1)
2297 @issue_status = IssueStatus.find(1)
2306 @issue_status = IssueStatus.find(1)
2298 @issue_status.update_attribute(:default_done_ratio, 50)
2307 @issue_status.update_attribute(:default_done_ratio, 50)
2299 @issue2 = Issue.find(2)
2308 @issue2 = Issue.find(2)
2300 @issue_status2 = IssueStatus.find(2)
2309 @issue_status2 = IssueStatus.find(2)
2301 @issue_status2.update_attribute(:default_done_ratio, 0)
2310 @issue_status2.update_attribute(:default_done_ratio, 0)
2302
2311
2303 with_settings :issue_done_ratio => 'issue_field' do
2312 with_settings :issue_done_ratio => 'issue_field' do
2304 @issue.update_done_ratio_from_issue_status
2313 @issue.update_done_ratio_from_issue_status
2305 @issue2.update_done_ratio_from_issue_status
2314 @issue2.update_done_ratio_from_issue_status
2306
2315
2307 assert_equal 0, @issue.read_attribute(:done_ratio)
2316 assert_equal 0, @issue.read_attribute(:done_ratio)
2308 assert_equal 30, @issue2.read_attribute(:done_ratio)
2317 assert_equal 30, @issue2.read_attribute(:done_ratio)
2309 end
2318 end
2310
2319
2311 with_settings :issue_done_ratio => 'issue_status' do
2320 with_settings :issue_done_ratio => 'issue_status' do
2312 @issue.update_done_ratio_from_issue_status
2321 @issue.update_done_ratio_from_issue_status
2313 @issue2.update_done_ratio_from_issue_status
2322 @issue2.update_done_ratio_from_issue_status
2314
2323
2315 assert_equal 50, @issue.read_attribute(:done_ratio)
2324 assert_equal 50, @issue.read_attribute(:done_ratio)
2316 assert_equal 0, @issue2.read_attribute(:done_ratio)
2325 assert_equal 0, @issue2.read_attribute(:done_ratio)
2317 end
2326 end
2318 end
2327 end
2319
2328
2320 test "#by_tracker" do
2329 test "#by_tracker" do
2321 User.current = User.anonymous
2330 User.current = User.anonymous
2322 groups = Issue.by_tracker(Project.find(1))
2331 groups = Issue.by_tracker(Project.find(1))
2323 assert_equal 3, groups.count
2332 assert_equal 3, groups.count
2324 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2333 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2325 end
2334 end
2326
2335
2327 test "#by_version" do
2336 test "#by_version" do
2328 User.current = User.anonymous
2337 User.current = User.anonymous
2329 groups = Issue.by_version(Project.find(1))
2338 groups = Issue.by_version(Project.find(1))
2330 assert_equal 3, groups.count
2339 assert_equal 3, groups.count
2331 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2340 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2332 end
2341 end
2333
2342
2334 test "#by_priority" do
2343 test "#by_priority" do
2335 User.current = User.anonymous
2344 User.current = User.anonymous
2336 groups = Issue.by_priority(Project.find(1))
2345 groups = Issue.by_priority(Project.find(1))
2337 assert_equal 4, groups.count
2346 assert_equal 4, groups.count
2338 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2347 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2339 end
2348 end
2340
2349
2341 test "#by_category" do
2350 test "#by_category" do
2342 User.current = User.anonymous
2351 User.current = User.anonymous
2343 groups = Issue.by_category(Project.find(1))
2352 groups = Issue.by_category(Project.find(1))
2344 assert_equal 2, groups.count
2353 assert_equal 2, groups.count
2345 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2354 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2346 end
2355 end
2347
2356
2348 test "#by_assigned_to" do
2357 test "#by_assigned_to" do
2349 User.current = User.anonymous
2358 User.current = User.anonymous
2350 groups = Issue.by_assigned_to(Project.find(1))
2359 groups = Issue.by_assigned_to(Project.find(1))
2351 assert_equal 2, groups.count
2360 assert_equal 2, groups.count
2352 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2361 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2353 end
2362 end
2354
2363
2355 test "#by_author" do
2364 test "#by_author" do
2356 User.current = User.anonymous
2365 User.current = User.anonymous
2357 groups = Issue.by_author(Project.find(1))
2366 groups = Issue.by_author(Project.find(1))
2358 assert_equal 4, groups.count
2367 assert_equal 4, groups.count
2359 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2368 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2360 end
2369 end
2361
2370
2362 test "#by_subproject" do
2371 test "#by_subproject" do
2363 User.current = User.anonymous
2372 User.current = User.anonymous
2364 groups = Issue.by_subproject(Project.find(1))
2373 groups = Issue.by_subproject(Project.find(1))
2365 # Private descendant not visible
2374 # Private descendant not visible
2366 assert_equal 1, groups.count
2375 assert_equal 1, groups.count
2367 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2376 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2368 end
2377 end
2369
2378
2370 def test_recently_updated_scope
2379 def test_recently_updated_scope
2371 #should return the last updated issue
2380 #should return the last updated issue
2372 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2381 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2373 end
2382 end
2374
2383
2375 def test_on_active_projects_scope
2384 def test_on_active_projects_scope
2376 assert Project.find(2).archive
2385 assert Project.find(2).archive
2377
2386
2378 before = Issue.on_active_project.length
2387 before = Issue.on_active_project.length
2379 # test inclusion to results
2388 # test inclusion to results
2380 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2389 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2381 assert_equal before + 1, Issue.on_active_project.length
2390 assert_equal before + 1, Issue.on_active_project.length
2382
2391
2383 # Move to an archived project
2392 # Move to an archived project
2384 issue.project = Project.find(2)
2393 issue.project = Project.find(2)
2385 assert issue.save
2394 assert issue.save
2386 assert_equal before, Issue.on_active_project.length
2395 assert_equal before, Issue.on_active_project.length
2387 end
2396 end
2388
2397
2389 test "Issue#recipients should include project recipients" do
2398 test "Issue#recipients should include project recipients" do
2390 issue = Issue.generate!
2399 issue = Issue.generate!
2391 assert issue.project.recipients.present?
2400 assert issue.project.recipients.present?
2392 issue.project.recipients.each do |project_recipient|
2401 issue.project.recipients.each do |project_recipient|
2393 assert issue.recipients.include?(project_recipient)
2402 assert issue.recipients.include?(project_recipient)
2394 end
2403 end
2395 end
2404 end
2396
2405
2397 test "Issue#recipients should include the author if the author is active" do
2406 test "Issue#recipients should include the author if the author is active" do
2398 issue = Issue.generate!(:author => User.generate!)
2407 issue = Issue.generate!(:author => User.generate!)
2399 assert issue.author, "No author set for Issue"
2408 assert issue.author, "No author set for Issue"
2400 assert issue.recipients.include?(issue.author.mail)
2409 assert issue.recipients.include?(issue.author.mail)
2401 end
2410 end
2402
2411
2403 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2412 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2404 issue = Issue.generate!(:assigned_to => User.generate!)
2413 issue = Issue.generate!(:assigned_to => User.generate!)
2405 assert issue.assigned_to, "No assigned_to set for Issue"
2414 assert issue.assigned_to, "No assigned_to set for Issue"
2406 assert issue.recipients.include?(issue.assigned_to.mail)
2415 assert issue.recipients.include?(issue.assigned_to.mail)
2407 end
2416 end
2408
2417
2409 test "Issue#recipients should not include users who opt out of all email" do
2418 test "Issue#recipients should not include users who opt out of all email" do
2410 issue = Issue.generate!(:author => User.generate!)
2419 issue = Issue.generate!(:author => User.generate!)
2411 issue.author.update_attribute(:mail_notification, :none)
2420 issue.author.update_attribute(:mail_notification, :none)
2412 assert !issue.recipients.include?(issue.author.mail)
2421 assert !issue.recipients.include?(issue.author.mail)
2413 end
2422 end
2414
2423
2415 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2424 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2416 issue = Issue.generate!(:author => User.generate!)
2425 issue = Issue.generate!(:author => User.generate!)
2417 issue.author.update_attribute(:mail_notification, :only_assigned)
2426 issue.author.update_attribute(:mail_notification, :only_assigned)
2418 assert !issue.recipients.include?(issue.author.mail)
2427 assert !issue.recipients.include?(issue.author.mail)
2419 end
2428 end
2420
2429
2421 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2430 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2422 issue = Issue.generate!(:assigned_to => User.generate!)
2431 issue = Issue.generate!(:assigned_to => User.generate!)
2423 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2432 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2424 assert !issue.recipients.include?(issue.assigned_to.mail)
2433 assert !issue.recipients.include?(issue.assigned_to.mail)
2425 end
2434 end
2426
2435
2427 def test_last_journal_id_with_journals_should_return_the_journal_id
2436 def test_last_journal_id_with_journals_should_return_the_journal_id
2428 assert_equal 2, Issue.find(1).last_journal_id
2437 assert_equal 2, Issue.find(1).last_journal_id
2429 end
2438 end
2430
2439
2431 def test_last_journal_id_without_journals_should_return_nil
2440 def test_last_journal_id_without_journals_should_return_nil
2432 assert_nil Issue.find(3).last_journal_id
2441 assert_nil Issue.find(3).last_journal_id
2433 end
2442 end
2434
2443
2435 def test_journals_after_should_return_journals_with_greater_id
2444 def test_journals_after_should_return_journals_with_greater_id
2436 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2445 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2437 assert_equal [], Issue.find(1).journals_after('2')
2446 assert_equal [], Issue.find(1).journals_after('2')
2438 end
2447 end
2439
2448
2440 def test_journals_after_with_blank_arg_should_return_all_journals
2449 def test_journals_after_with_blank_arg_should_return_all_journals
2441 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2450 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2442 end
2451 end
2443
2452
2444 def test_css_classes_should_include_tracker
2453 def test_css_classes_should_include_tracker
2445 issue = Issue.new(:tracker => Tracker.find(2))
2454 issue = Issue.new(:tracker => Tracker.find(2))
2446 classes = issue.css_classes.split(' ')
2455 classes = issue.css_classes.split(' ')
2447 assert_include 'tracker-2', classes
2456 assert_include 'tracker-2', classes
2448 end
2457 end
2449
2458
2450 def test_css_classes_should_include_priority
2459 def test_css_classes_should_include_priority
2451 issue = Issue.new(:priority => IssuePriority.find(8))
2460 issue = Issue.new(:priority => IssuePriority.find(8))
2452 classes = issue.css_classes.split(' ')
2461 classes = issue.css_classes.split(' ')
2453 assert_include 'priority-8', classes
2462 assert_include 'priority-8', classes
2454 assert_include 'priority-highest', classes
2463 assert_include 'priority-highest', classes
2455 end
2464 end
2456
2465
2457 def test_css_classes_should_include_user_and_group_assignment
2466 def test_css_classes_should_include_user_and_group_assignment
2458 project = Project.first
2467 project = Project.first
2459 user = User.generate!
2468 user = User.generate!
2460 group = Group.generate!
2469 group = Group.generate!
2461 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2470 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2462 group.users << user
2471 group.users << user
2463 assert user.member_of?(project)
2472 assert user.member_of?(project)
2464 issue1 = Issue.generate(:assigned_to_id => group.id)
2473 issue1 = Issue.generate(:assigned_to_id => group.id)
2465 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2474 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2466 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2475 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2467 issue2 = Issue.generate(:assigned_to_id => user.id)
2476 issue2 = Issue.generate(:assigned_to_id => user.id)
2468 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2477 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2469 assert_include 'assigned-to-me', issue2.css_classes(user)
2478 assert_include 'assigned-to-me', issue2.css_classes(user)
2470 end
2479 end
2471
2480
2472 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2481 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2473 set_tmp_attachments_directory
2482 set_tmp_attachments_directory
2474 issue = Issue.generate!
2483 issue = Issue.generate!
2475 issue.save_attachments({
2484 issue.save_attachments({
2476 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2485 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2477 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2486 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2478 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2487 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2479 })
2488 })
2480 issue.attach_saved_attachments
2489 issue.attach_saved_attachments
2481
2490
2482 assert_equal 3, issue.reload.attachments.count
2491 assert_equal 3, issue.reload.attachments.count
2483 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2492 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2484 end
2493 end
2485
2494
2486 def test_closed_on_should_be_nil_when_creating_an_open_issue
2495 def test_closed_on_should_be_nil_when_creating_an_open_issue
2487 issue = Issue.generate!(:status_id => 1).reload
2496 issue = Issue.generate!(:status_id => 1).reload
2488 assert !issue.closed?
2497 assert !issue.closed?
2489 assert_nil issue.closed_on
2498 assert_nil issue.closed_on
2490 end
2499 end
2491
2500
2492 def test_closed_on_should_be_set_when_creating_a_closed_issue
2501 def test_closed_on_should_be_set_when_creating_a_closed_issue
2493 issue = Issue.generate!(:status_id => 5).reload
2502 issue = Issue.generate!(:status_id => 5).reload
2494 assert issue.closed?
2503 assert issue.closed?
2495 assert_not_nil issue.closed_on
2504 assert_not_nil issue.closed_on
2496 assert_equal issue.updated_on, issue.closed_on
2505 assert_equal issue.updated_on, issue.closed_on
2497 assert_equal issue.created_on, issue.closed_on
2506 assert_equal issue.created_on, issue.closed_on
2498 end
2507 end
2499
2508
2500 def test_closed_on_should_be_nil_when_updating_an_open_issue
2509 def test_closed_on_should_be_nil_when_updating_an_open_issue
2501 issue = Issue.find(1)
2510 issue = Issue.find(1)
2502 issue.subject = 'Not closed yet'
2511 issue.subject = 'Not closed yet'
2503 issue.save!
2512 issue.save!
2504 issue.reload
2513 issue.reload
2505 assert_nil issue.closed_on
2514 assert_nil issue.closed_on
2506 end
2515 end
2507
2516
2508 def test_closed_on_should_be_set_when_closing_an_open_issue
2517 def test_closed_on_should_be_set_when_closing_an_open_issue
2509 issue = Issue.find(1)
2518 issue = Issue.find(1)
2510 issue.subject = 'Now closed'
2519 issue.subject = 'Now closed'
2511 issue.status_id = 5
2520 issue.status_id = 5
2512 issue.save!
2521 issue.save!
2513 issue.reload
2522 issue.reload
2514 assert_not_nil issue.closed_on
2523 assert_not_nil issue.closed_on
2515 assert_equal issue.updated_on, issue.closed_on
2524 assert_equal issue.updated_on, issue.closed_on
2516 end
2525 end
2517
2526
2518 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2527 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2519 issue = Issue.open(false).first
2528 issue = Issue.open(false).first
2520 was_closed_on = issue.closed_on
2529 was_closed_on = issue.closed_on
2521 assert_not_nil was_closed_on
2530 assert_not_nil was_closed_on
2522 issue.subject = 'Updating a closed issue'
2531 issue.subject = 'Updating a closed issue'
2523 issue.save!
2532 issue.save!
2524 issue.reload
2533 issue.reload
2525 assert_equal was_closed_on, issue.closed_on
2534 assert_equal was_closed_on, issue.closed_on
2526 end
2535 end
2527
2536
2528 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2537 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2529 issue = Issue.open(false).first
2538 issue = Issue.open(false).first
2530 was_closed_on = issue.closed_on
2539 was_closed_on = issue.closed_on
2531 assert_not_nil was_closed_on
2540 assert_not_nil was_closed_on
2532 issue.subject = 'Reopening a closed issue'
2541 issue.subject = 'Reopening a closed issue'
2533 issue.status_id = 1
2542 issue.status_id = 1
2534 issue.save!
2543 issue.save!
2535 issue.reload
2544 issue.reload
2536 assert !issue.closed?
2545 assert !issue.closed?
2537 assert_equal was_closed_on, issue.closed_on
2546 assert_equal was_closed_on, issue.closed_on
2538 end
2547 end
2539
2548
2540 def test_status_was_should_return_nil_for_new_issue
2549 def test_status_was_should_return_nil_for_new_issue
2541 issue = Issue.new
2550 issue = Issue.new
2542 assert_nil issue.status_was
2551 assert_nil issue.status_was
2543 end
2552 end
2544
2553
2545 def test_status_was_should_return_status_before_change
2554 def test_status_was_should_return_status_before_change
2546 issue = Issue.find(1)
2555 issue = Issue.find(1)
2547 issue.status = IssueStatus.find(2)
2556 issue.status = IssueStatus.find(2)
2548 assert_equal IssueStatus.find(1), issue.status_was
2557 assert_equal IssueStatus.find(1), issue.status_was
2549 end
2558 end
2550
2559
2551 def test_status_was_should_return_status_before_change_with_status_id
2560 def test_status_was_should_return_status_before_change_with_status_id
2552 issue = Issue.find(1)
2561 issue = Issue.find(1)
2553 assert_equal IssueStatus.find(1), issue.status
2562 assert_equal IssueStatus.find(1), issue.status
2554 issue.status_id = 2
2563 issue.status_id = 2
2555 assert_equal IssueStatus.find(1), issue.status_was
2564 assert_equal IssueStatus.find(1), issue.status_was
2556 end
2565 end
2557
2566
2558 def test_status_was_should_be_reset_on_save
2567 def test_status_was_should_be_reset_on_save
2559 issue = Issue.find(1)
2568 issue = Issue.find(1)
2560 issue.status = IssueStatus.find(2)
2569 issue.status = IssueStatus.find(2)
2561 assert_equal IssueStatus.find(1), issue.status_was
2570 assert_equal IssueStatus.find(1), issue.status_was
2562 assert issue.save!
2571 assert issue.save!
2563 assert_equal IssueStatus.find(2), issue.status_was
2572 assert_equal IssueStatus.find(2), issue.status_was
2564 end
2573 end
2565
2574
2566 def test_closing_should_return_true_when_closing_an_issue
2575 def test_closing_should_return_true_when_closing_an_issue
2567 issue = Issue.find(1)
2576 issue = Issue.find(1)
2568 issue.status = IssueStatus.find(2)
2577 issue.status = IssueStatus.find(2)
2569 assert_equal false, issue.closing?
2578 assert_equal false, issue.closing?
2570 issue.status = IssueStatus.find(5)
2579 issue.status = IssueStatus.find(5)
2571 assert_equal true, issue.closing?
2580 assert_equal true, issue.closing?
2572 end
2581 end
2573
2582
2574 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2583 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2575 issue = Issue.find(1)
2584 issue = Issue.find(1)
2576 issue.status_id = 2
2585 issue.status_id = 2
2577 assert_equal false, issue.closing?
2586 assert_equal false, issue.closing?
2578 issue.status_id = 5
2587 issue.status_id = 5
2579 assert_equal true, issue.closing?
2588 assert_equal true, issue.closing?
2580 end
2589 end
2581
2590
2582 def test_closing_should_return_true_for_new_closed_issue
2591 def test_closing_should_return_true_for_new_closed_issue
2583 issue = Issue.new
2592 issue = Issue.new
2584 assert_equal false, issue.closing?
2593 assert_equal false, issue.closing?
2585 issue.status = IssueStatus.find(5)
2594 issue.status = IssueStatus.find(5)
2586 assert_equal true, issue.closing?
2595 assert_equal true, issue.closing?
2587 end
2596 end
2588
2597
2589 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2598 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2590 issue = Issue.new
2599 issue = Issue.new
2591 assert_equal false, issue.closing?
2600 assert_equal false, issue.closing?
2592 issue.status_id = 5
2601 issue.status_id = 5
2593 assert_equal true, issue.closing?
2602 assert_equal true, issue.closing?
2594 end
2603 end
2595
2604
2596 def test_closing_should_be_reset_after_save
2605 def test_closing_should_be_reset_after_save
2597 issue = Issue.find(1)
2606 issue = Issue.find(1)
2598 issue.status_id = 5
2607 issue.status_id = 5
2599 assert_equal true, issue.closing?
2608 assert_equal true, issue.closing?
2600 issue.save!
2609 issue.save!
2601 assert_equal false, issue.closing?
2610 assert_equal false, issue.closing?
2602 end
2611 end
2603
2612
2604 def test_reopening_should_return_true_when_reopening_an_issue
2613 def test_reopening_should_return_true_when_reopening_an_issue
2605 issue = Issue.find(8)
2614 issue = Issue.find(8)
2606 issue.status = IssueStatus.find(6)
2615 issue.status = IssueStatus.find(6)
2607 assert_equal false, issue.reopening?
2616 assert_equal false, issue.reopening?
2608 issue.status = IssueStatus.find(2)
2617 issue.status = IssueStatus.find(2)
2609 assert_equal true, issue.reopening?
2618 assert_equal true, issue.reopening?
2610 end
2619 end
2611
2620
2612 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2621 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2613 issue = Issue.find(8)
2622 issue = Issue.find(8)
2614 issue.status_id = 6
2623 issue.status_id = 6
2615 assert_equal false, issue.reopening?
2624 assert_equal false, issue.reopening?
2616 issue.status_id = 2
2625 issue.status_id = 2
2617 assert_equal true, issue.reopening?
2626 assert_equal true, issue.reopening?
2618 end
2627 end
2619
2628
2620 def test_reopening_should_return_false_for_new_open_issue
2629 def test_reopening_should_return_false_for_new_open_issue
2621 issue = Issue.new
2630 issue = Issue.new
2622 issue.status = IssueStatus.find(1)
2631 issue.status = IssueStatus.find(1)
2623 assert_equal false, issue.reopening?
2632 assert_equal false, issue.reopening?
2624 end
2633 end
2625
2634
2626 def test_reopening_should_be_reset_after_save
2635 def test_reopening_should_be_reset_after_save
2627 issue = Issue.find(8)
2636 issue = Issue.find(8)
2628 issue.status_id = 2
2637 issue.status_id = 2
2629 assert_equal true, issue.reopening?
2638 assert_equal true, issue.reopening?
2630 issue.save!
2639 issue.save!
2631 assert_equal false, issue.reopening?
2640 assert_equal false, issue.reopening?
2632 end
2641 end
2633
2642
2634 def test_default_status_without_tracker_should_be_nil
2643 def test_default_status_without_tracker_should_be_nil
2635 issue = Issue.new
2644 issue = Issue.new
2636 assert_nil issue.tracker
2645 assert_nil issue.tracker
2637 assert_nil issue.default_status
2646 assert_nil issue.default_status
2638 end
2647 end
2639
2648
2640 def test_default_status_should_be_tracker_default_status
2649 def test_default_status_should_be_tracker_default_status
2641 issue = Issue.new(:tracker_id => 1)
2650 issue = Issue.new(:tracker_id => 1)
2642 assert_not_nil issue.status
2651 assert_not_nil issue.status
2643 assert_equal issue.tracker.default_status, issue.default_status
2652 assert_equal issue.tracker.default_status, issue.default_status
2644 end
2653 end
2645
2654
2646 def test_initializing_with_tracker_should_set_default_status
2655 def test_initializing_with_tracker_should_set_default_status
2647 issue = Issue.new(:tracker => Tracker.find(1))
2656 issue = Issue.new(:tracker => Tracker.find(1))
2648 assert_not_nil issue.status
2657 assert_not_nil issue.status
2649 assert_equal issue.default_status, issue.status
2658 assert_equal issue.default_status, issue.status
2650 end
2659 end
2651
2660
2652 def test_initializing_with_tracker_id_should_set_default_status
2661 def test_initializing_with_tracker_id_should_set_default_status
2653 issue = Issue.new(:tracker_id => 1)
2662 issue = Issue.new(:tracker_id => 1)
2654 assert_not_nil issue.status
2663 assert_not_nil issue.status
2655 assert_equal issue.default_status, issue.status
2664 assert_equal issue.default_status, issue.status
2656 end
2665 end
2657
2666
2658 def test_setting_tracker_should_set_default_status
2667 def test_setting_tracker_should_set_default_status
2659 issue = Issue.new
2668 issue = Issue.new
2660 issue.tracker = Tracker.find(1)
2669 issue.tracker = Tracker.find(1)
2661 assert_not_nil issue.status
2670 assert_not_nil issue.status
2662 assert_equal issue.default_status, issue.status
2671 assert_equal issue.default_status, issue.status
2663 end
2672 end
2664
2673
2665 def test_changing_tracker_should_set_default_status_if_status_was_default
2674 def test_changing_tracker_should_set_default_status_if_status_was_default
2666 WorkflowTransition.delete_all
2675 WorkflowTransition.delete_all
2667 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2676 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2668 Tracker.find(2).update! :default_status_id => 2
2677 Tracker.find(2).update! :default_status_id => 2
2669
2678
2670 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2679 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2671 assert_equal IssueStatus.find(1), issue.status
2680 assert_equal IssueStatus.find(1), issue.status
2672 issue.tracker = Tracker.find(2)
2681 issue.tracker = Tracker.find(2)
2673 assert_equal IssueStatus.find(2), issue.status
2682 assert_equal IssueStatus.find(2), issue.status
2674 end
2683 end
2675
2684
2676 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2685 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2677 WorkflowTransition.delete_all
2686 WorkflowTransition.delete_all
2678 Tracker.find(2).update! :default_status_id => 2
2687 Tracker.find(2).update! :default_status_id => 2
2679
2688
2680 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2689 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2681 assert_equal IssueStatus.find(3), issue.status
2690 assert_equal IssueStatus.find(3), issue.status
2682 issue.tracker = Tracker.find(2)
2691 issue.tracker = Tracker.find(2)
2683 assert_equal IssueStatus.find(2), issue.status
2692 assert_equal IssueStatus.find(2), issue.status
2684 end
2693 end
2685
2694
2686 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2695 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2687 WorkflowTransition.delete_all
2696 WorkflowTransition.delete_all
2688 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2697 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2689 Tracker.find(2).update! :default_status_id => 2
2698 Tracker.find(2).update! :default_status_id => 2
2690
2699
2691 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2700 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2692 assert_equal IssueStatus.find(3), issue.status
2701 assert_equal IssueStatus.find(3), issue.status
2693 issue.tracker = Tracker.find(2)
2702 issue.tracker = Tracker.find(2)
2694 assert_equal IssueStatus.find(3), issue.status
2703 assert_equal IssueStatus.find(3), issue.status
2695 end
2704 end
2696
2705
2697 def test_assigned_to_was_with_a_group
2706 def test_assigned_to_was_with_a_group
2698 group = Group.find(10)
2707 group = Group.find(10)
2699
2708
2700 issue = Issue.generate!(:assigned_to => group)
2709 issue = Issue.generate!(:assigned_to => group)
2701 issue.reload.assigned_to = nil
2710 issue.reload.assigned_to = nil
2702 assert_equal group, issue.assigned_to_was
2711 assert_equal group, issue.assigned_to_was
2703 end
2712 end
2704 end
2713 end
General Comments 0
You need to be logged in to leave comments. Login now