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