##// END OF EJS Templates
Change Project#notified_users to check for the 'all' notification option. #6541...
Eric Davis -
r4133:700c302fca1e
parent child
Show More
@@ -1,774 +1,775
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 # Project statuses
19 # Project statuses
20 STATUS_ACTIVE = 1
20 STATUS_ACTIVE = 1
21 STATUS_ARCHIVED = 9
21 STATUS_ARCHIVED = 9
22
22
23 # Specific overidden Activities
23 # Specific overidden Activities
24 has_many :time_entry_activities
24 has_many :time_entry_activities
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
25 has_many :members, :include => [:user, :roles], :conditions => "#{User.table_name}.type='User' AND #{User.table_name}.status=#{User::STATUS_ACTIVE}"
26 has_many :memberships, :class_name => 'Member'
26 has_many :memberships, :class_name => 'Member'
27 has_many :member_principals, :class_name => 'Member',
27 has_many :member_principals, :class_name => 'Member',
28 :include => :principal,
28 :include => :principal,
29 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
29 :conditions => "#{Principal.table_name}.type='Group' OR (#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{User::STATUS_ACTIVE})"
30 has_many :users, :through => :members
30 has_many :users, :through => :members
31 has_many :principals, :through => :member_principals, :source => :principal
31 has_many :principals, :through => :member_principals, :source => :principal
32
32
33 has_many :enabled_modules, :dependent => :delete_all
33 has_many :enabled_modules, :dependent => :delete_all
34 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
34 has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position"
35 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
35 has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
36 has_many :issue_changes, :through => :issues, :source => :journals
36 has_many :issue_changes, :through => :issues, :source => :journals
37 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
37 has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC"
38 has_many :time_entries, :dependent => :delete_all
38 has_many :time_entries, :dependent => :delete_all
39 has_many :queries, :dependent => :delete_all
39 has_many :queries, :dependent => :delete_all
40 has_many :documents, :dependent => :destroy
40 has_many :documents, :dependent => :destroy
41 has_many :news, :dependent => :delete_all, :include => :author
41 has_many :news, :dependent => :delete_all, :include => :author
42 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
42 has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name"
43 has_many :boards, :dependent => :destroy, :order => "position ASC"
43 has_many :boards, :dependent => :destroy, :order => "position ASC"
44 has_one :repository, :dependent => :destroy
44 has_one :repository, :dependent => :destroy
45 has_many :changesets, :through => :repository
45 has_many :changesets, :through => :repository
46 has_one :wiki, :dependent => :destroy
46 has_one :wiki, :dependent => :destroy
47 # Custom field for the project issues
47 # Custom field for the project issues
48 has_and_belongs_to_many :issue_custom_fields,
48 has_and_belongs_to_many :issue_custom_fields,
49 :class_name => 'IssueCustomField',
49 :class_name => 'IssueCustomField',
50 :order => "#{CustomField.table_name}.position",
50 :order => "#{CustomField.table_name}.position",
51 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
51 :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}",
52 :association_foreign_key => 'custom_field_id'
52 :association_foreign_key => 'custom_field_id'
53
53
54 acts_as_nested_set :order => 'name'
54 acts_as_nested_set :order => 'name'
55 acts_as_attachable :view_permission => :view_files,
55 acts_as_attachable :view_permission => :view_files,
56 :delete_permission => :manage_files
56 :delete_permission => :manage_files
57
57
58 acts_as_customizable
58 acts_as_customizable
59 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
59 acts_as_searchable :columns => ['name', 'identifier', 'description'], :project_key => 'id', :permission => nil
60 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
60 acts_as_event :title => Proc.new {|o| "#{l(:label_project)}: #{o.name}"},
61 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
61 :url => Proc.new {|o| {:controller => 'projects', :action => 'show', :id => o}},
62 :author => nil
62 :author => nil
63
63
64 attr_protected :status, :enabled_module_names
64 attr_protected :status, :enabled_module_names
65
65
66 validates_presence_of :name, :identifier
66 validates_presence_of :name, :identifier
67 validates_uniqueness_of :name, :identifier
67 validates_uniqueness_of :name, :identifier
68 validates_associated :repository, :wiki
68 validates_associated :repository, :wiki
69 validates_length_of :name, :maximum => 30
69 validates_length_of :name, :maximum => 30
70 validates_length_of :homepage, :maximum => 255
70 validates_length_of :homepage, :maximum => 255
71 validates_length_of :identifier, :in => 1..20
71 validates_length_of :identifier, :in => 1..20
72 # donwcase letters, digits, dashes but not digits only
72 # donwcase letters, digits, dashes but not digits only
73 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
73 validates_format_of :identifier, :with => /^(?!\d+$)[a-z0-9\-]*$/, :if => Proc.new { |p| p.identifier_changed? }
74 # reserved words
74 # reserved words
75 validates_exclusion_of :identifier, :in => %w( new )
75 validates_exclusion_of :identifier, :in => %w( new )
76
76
77 before_destroy :delete_all_members, :destroy_children
77 before_destroy :delete_all_members, :destroy_children
78
78
79 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
79 named_scope :has_module, lambda { |mod| { :conditions => ["#{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name=?)", mod.to_s] } }
80 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
80 named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
81 named_scope :all_public, { :conditions => { :is_public => true } }
81 named_scope :all_public, { :conditions => { :is_public => true } }
82 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
82 named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
83
83
84 def identifier=(identifier)
84 def identifier=(identifier)
85 super unless identifier_frozen?
85 super unless identifier_frozen?
86 end
86 end
87
87
88 def identifier_frozen?
88 def identifier_frozen?
89 errors[:identifier].nil? && !(new_record? || identifier.blank?)
89 errors[:identifier].nil? && !(new_record? || identifier.blank?)
90 end
90 end
91
91
92 # returns latest created projects
92 # returns latest created projects
93 # non public projects will be returned only if user is a member of those
93 # non public projects will be returned only if user is a member of those
94 def self.latest(user=nil, count=5)
94 def self.latest(user=nil, count=5)
95 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
95 find(:all, :limit => count, :conditions => visible_by(user), :order => "created_on DESC")
96 end
96 end
97
97
98 # Returns a SQL :conditions string used to find all active projects for the specified user.
98 # Returns a SQL :conditions string used to find all active projects for the specified user.
99 #
99 #
100 # Examples:
100 # Examples:
101 # Projects.visible_by(admin) => "projects.status = 1"
101 # Projects.visible_by(admin) => "projects.status = 1"
102 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
102 # Projects.visible_by(normal_user) => "projects.status = 1 AND projects.is_public = 1"
103 def self.visible_by(user=nil)
103 def self.visible_by(user=nil)
104 user ||= User.current
104 user ||= User.current
105 if user && user.admin?
105 if user && user.admin?
106 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
106 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
107 elsif user && user.memberships.any?
107 elsif user && user.memberships.any?
108 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
108 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND (#{Project.table_name}.is_public = #{connection.quoted_true} or #{Project.table_name}.id IN (#{user.memberships.collect{|m| m.project_id}.join(',')}))"
109 else
109 else
110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
110 return "#{Project.table_name}.status=#{Project::STATUS_ACTIVE} AND #{Project.table_name}.is_public = #{connection.quoted_true}"
111 end
111 end
112 end
112 end
113
113
114 def self.allowed_to_condition(user, permission, options={})
114 def self.allowed_to_condition(user, permission, options={})
115 statements = []
115 statements = []
116 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
116 base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
117 if perm = Redmine::AccessControl.permission(permission)
117 if perm = Redmine::AccessControl.permission(permission)
118 unless perm.project_module.nil?
118 unless perm.project_module.nil?
119 # If the permission belongs to a project module, make sure the module is enabled
119 # If the permission belongs to a project module, make sure the module is enabled
120 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
120 base_statement << " AND #{Project.table_name}.id IN (SELECT em.project_id FROM #{EnabledModule.table_name} em WHERE em.name='#{perm.project_module}')"
121 end
121 end
122 end
122 end
123 if options[:project]
123 if options[:project]
124 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
124 project_statement = "#{Project.table_name}.id = #{options[:project].id}"
125 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
125 project_statement << " OR (#{Project.table_name}.lft > #{options[:project].lft} AND #{Project.table_name}.rgt < #{options[:project].rgt})" if options[:with_subprojects]
126 base_statement = "(#{project_statement}) AND (#{base_statement})"
126 base_statement = "(#{project_statement}) AND (#{base_statement})"
127 end
127 end
128 if user.admin?
128 if user.admin?
129 # no restriction
129 # no restriction
130 else
130 else
131 statements << "1=0"
131 statements << "1=0"
132 if user.logged?
132 if user.logged?
133 if Role.non_member.allowed_to?(permission) && !options[:member]
133 if Role.non_member.allowed_to?(permission) && !options[:member]
134 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
134 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
135 end
135 end
136 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
136 allowed_project_ids = user.memberships.select {|m| m.roles.detect {|role| role.allowed_to?(permission)}}.collect {|m| m.project_id}
137 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
137 statements << "#{Project.table_name}.id IN (#{allowed_project_ids.join(',')})" if allowed_project_ids.any?
138 else
138 else
139 if Role.anonymous.allowed_to?(permission) && !options[:member]
139 if Role.anonymous.allowed_to?(permission) && !options[:member]
140 # anonymous user allowed on public project
140 # anonymous user allowed on public project
141 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
141 statements << "#{Project.table_name}.is_public = #{connection.quoted_true}"
142 end
142 end
143 end
143 end
144 end
144 end
145 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
145 statements.empty? ? base_statement : "((#{base_statement}) AND (#{statements.join(' OR ')}))"
146 end
146 end
147
147
148 # Returns the Systemwide and project specific activities
148 # Returns the Systemwide and project specific activities
149 def activities(include_inactive=false)
149 def activities(include_inactive=false)
150 if include_inactive
150 if include_inactive
151 return all_activities
151 return all_activities
152 else
152 else
153 return active_activities
153 return active_activities
154 end
154 end
155 end
155 end
156
156
157 # Will create a new Project specific Activity or update an existing one
157 # Will create a new Project specific Activity or update an existing one
158 #
158 #
159 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
159 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
160 # does not successfully save.
160 # does not successfully save.
161 def update_or_create_time_entry_activity(id, activity_hash)
161 def update_or_create_time_entry_activity(id, activity_hash)
162 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
162 if activity_hash.respond_to?(:has_key?) && activity_hash.has_key?('parent_id')
163 self.create_time_entry_activity_if_needed(activity_hash)
163 self.create_time_entry_activity_if_needed(activity_hash)
164 else
164 else
165 activity = project.time_entry_activities.find_by_id(id.to_i)
165 activity = project.time_entry_activities.find_by_id(id.to_i)
166 activity.update_attributes(activity_hash) if activity
166 activity.update_attributes(activity_hash) if activity
167 end
167 end
168 end
168 end
169
169
170 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
170 # Create a new TimeEntryActivity if it overrides a system TimeEntryActivity
171 #
171 #
172 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
172 # This will raise a ActiveRecord::Rollback if the TimeEntryActivity
173 # does not successfully save.
173 # does not successfully save.
174 def create_time_entry_activity_if_needed(activity)
174 def create_time_entry_activity_if_needed(activity)
175 if activity['parent_id']
175 if activity['parent_id']
176
176
177 parent_activity = TimeEntryActivity.find(activity['parent_id'])
177 parent_activity = TimeEntryActivity.find(activity['parent_id'])
178 activity['name'] = parent_activity.name
178 activity['name'] = parent_activity.name
179 activity['position'] = parent_activity.position
179 activity['position'] = parent_activity.position
180
180
181 if Enumeration.overridding_change?(activity, parent_activity)
181 if Enumeration.overridding_change?(activity, parent_activity)
182 project_activity = self.time_entry_activities.create(activity)
182 project_activity = self.time_entry_activities.create(activity)
183
183
184 if project_activity.new_record?
184 if project_activity.new_record?
185 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
185 raise ActiveRecord::Rollback, "Overridding TimeEntryActivity was not successfully saved"
186 else
186 else
187 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
187 self.time_entries.update_all("activity_id = #{project_activity.id}", ["activity_id = ?", parent_activity.id])
188 end
188 end
189 end
189 end
190 end
190 end
191 end
191 end
192
192
193 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
193 # Returns a :conditions SQL string that can be used to find the issues associated with this project.
194 #
194 #
195 # Examples:
195 # Examples:
196 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
196 # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))"
197 # project.project_condition(false) => "projects.id = 1"
197 # project.project_condition(false) => "projects.id = 1"
198 def project_condition(with_subprojects)
198 def project_condition(with_subprojects)
199 cond = "#{Project.table_name}.id = #{id}"
199 cond = "#{Project.table_name}.id = #{id}"
200 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
200 cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt}))" if with_subprojects
201 cond
201 cond
202 end
202 end
203
203
204 def self.find(*args)
204 def self.find(*args)
205 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
205 if args.first && args.first.is_a?(String) && !args.first.match(/^\d*$/)
206 project = find_by_identifier(*args)
206 project = find_by_identifier(*args)
207 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
207 raise ActiveRecord::RecordNotFound, "Couldn't find Project with identifier=#{args.first}" if project.nil?
208 project
208 project
209 else
209 else
210 super
210 super
211 end
211 end
212 end
212 end
213
213
214 def to_param
214 def to_param
215 # id is used for projects with a numeric identifier (compatibility)
215 # id is used for projects with a numeric identifier (compatibility)
216 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
216 @to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)
217 end
217 end
218
218
219 def active?
219 def active?
220 self.status == STATUS_ACTIVE
220 self.status == STATUS_ACTIVE
221 end
221 end
222
222
223 # Archives the project and its descendants
223 # Archives the project and its descendants
224 def archive
224 def archive
225 # Check that there is no issue of a non descendant project that is assigned
225 # Check that there is no issue of a non descendant project that is assigned
226 # to one of the project or descendant versions
226 # to one of the project or descendant versions
227 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
227 v_ids = self_and_descendants.collect {|p| p.version_ids}.flatten
228 if v_ids.any? && Issue.find(:first, :include => :project,
228 if v_ids.any? && Issue.find(:first, :include => :project,
229 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
229 :conditions => ["(#{Project.table_name}.lft < ? OR #{Project.table_name}.rgt > ?)" +
230 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
230 " AND #{Issue.table_name}.fixed_version_id IN (?)", lft, rgt, v_ids])
231 return false
231 return false
232 end
232 end
233 Project.transaction do
233 Project.transaction do
234 archive!
234 archive!
235 end
235 end
236 true
236 true
237 end
237 end
238
238
239 # Unarchives the project
239 # Unarchives the project
240 # All its ancestors must be active
240 # All its ancestors must be active
241 def unarchive
241 def unarchive
242 return false if ancestors.detect {|a| !a.active?}
242 return false if ancestors.detect {|a| !a.active?}
243 update_attribute :status, STATUS_ACTIVE
243 update_attribute :status, STATUS_ACTIVE
244 end
244 end
245
245
246 # Returns an array of projects the project can be moved to
246 # Returns an array of projects the project can be moved to
247 # by the current user
247 # by the current user
248 def allowed_parents
248 def allowed_parents
249 return @allowed_parents if @allowed_parents
249 return @allowed_parents if @allowed_parents
250 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
250 @allowed_parents = Project.find(:all, :conditions => Project.allowed_to_condition(User.current, :add_subprojects))
251 @allowed_parents = @allowed_parents - self_and_descendants
251 @allowed_parents = @allowed_parents - self_and_descendants
252 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
252 if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?)
253 @allowed_parents << nil
253 @allowed_parents << nil
254 end
254 end
255 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
255 unless parent.nil? || @allowed_parents.empty? || @allowed_parents.include?(parent)
256 @allowed_parents << parent
256 @allowed_parents << parent
257 end
257 end
258 @allowed_parents
258 @allowed_parents
259 end
259 end
260
260
261 # Sets the parent of the project with authorization check
261 # Sets the parent of the project with authorization check
262 def set_allowed_parent!(p)
262 def set_allowed_parent!(p)
263 unless p.nil? || p.is_a?(Project)
263 unless p.nil? || p.is_a?(Project)
264 if p.to_s.blank?
264 if p.to_s.blank?
265 p = nil
265 p = nil
266 else
266 else
267 p = Project.find_by_id(p)
267 p = Project.find_by_id(p)
268 return false unless p
268 return false unless p
269 end
269 end
270 end
270 end
271 if p.nil?
271 if p.nil?
272 if !new_record? && allowed_parents.empty?
272 if !new_record? && allowed_parents.empty?
273 return false
273 return false
274 end
274 end
275 elsif !allowed_parents.include?(p)
275 elsif !allowed_parents.include?(p)
276 return false
276 return false
277 end
277 end
278 set_parent!(p)
278 set_parent!(p)
279 end
279 end
280
280
281 # Sets the parent of the project
281 # Sets the parent of the project
282 # Argument can be either a Project, a String, a Fixnum or nil
282 # Argument can be either a Project, a String, a Fixnum or nil
283 def set_parent!(p)
283 def set_parent!(p)
284 unless p.nil? || p.is_a?(Project)
284 unless p.nil? || p.is_a?(Project)
285 if p.to_s.blank?
285 if p.to_s.blank?
286 p = nil
286 p = nil
287 else
287 else
288 p = Project.find_by_id(p)
288 p = Project.find_by_id(p)
289 return false unless p
289 return false unless p
290 end
290 end
291 end
291 end
292 if p == parent && !p.nil?
292 if p == parent && !p.nil?
293 # Nothing to do
293 # Nothing to do
294 true
294 true
295 elsif p.nil? || (p.active? && move_possible?(p))
295 elsif p.nil? || (p.active? && move_possible?(p))
296 # Insert the project so that target's children or root projects stay alphabetically sorted
296 # Insert the project so that target's children or root projects stay alphabetically sorted
297 sibs = (p.nil? ? self.class.roots : p.children)
297 sibs = (p.nil? ? self.class.roots : p.children)
298 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
298 to_be_inserted_before = sibs.detect {|c| c.name.to_s.downcase > name.to_s.downcase }
299 if to_be_inserted_before
299 if to_be_inserted_before
300 move_to_left_of(to_be_inserted_before)
300 move_to_left_of(to_be_inserted_before)
301 elsif p.nil?
301 elsif p.nil?
302 if sibs.empty?
302 if sibs.empty?
303 # move_to_root adds the project in first (ie. left) position
303 # move_to_root adds the project in first (ie. left) position
304 move_to_root
304 move_to_root
305 else
305 else
306 move_to_right_of(sibs.last) unless self == sibs.last
306 move_to_right_of(sibs.last) unless self == sibs.last
307 end
307 end
308 else
308 else
309 # move_to_child_of adds the project in last (ie.right) position
309 # move_to_child_of adds the project in last (ie.right) position
310 move_to_child_of(p)
310 move_to_child_of(p)
311 end
311 end
312 Issue.update_versions_from_hierarchy_change(self)
312 Issue.update_versions_from_hierarchy_change(self)
313 true
313 true
314 else
314 else
315 # Can not move to the given target
315 # Can not move to the given target
316 false
316 false
317 end
317 end
318 end
318 end
319
319
320 # Returns an array of the trackers used by the project and its active sub projects
320 # Returns an array of the trackers used by the project and its active sub projects
321 def rolled_up_trackers
321 def rolled_up_trackers
322 @rolled_up_trackers ||=
322 @rolled_up_trackers ||=
323 Tracker.find(:all, :include => :projects,
323 Tracker.find(:all, :include => :projects,
324 :select => "DISTINCT #{Tracker.table_name}.*",
324 :select => "DISTINCT #{Tracker.table_name}.*",
325 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
325 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt],
326 :order => "#{Tracker.table_name}.position")
326 :order => "#{Tracker.table_name}.position")
327 end
327 end
328
328
329 # Closes open and locked project versions that are completed
329 # Closes open and locked project versions that are completed
330 def close_completed_versions
330 def close_completed_versions
331 Version.transaction do
331 Version.transaction do
332 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
332 versions.find(:all, :conditions => {:status => %w(open locked)}).each do |version|
333 if version.completed?
333 if version.completed?
334 version.update_attribute(:status, 'closed')
334 version.update_attribute(:status, 'closed')
335 end
335 end
336 end
336 end
337 end
337 end
338 end
338 end
339
339
340 # Returns a scope of the Versions on subprojects
340 # Returns a scope of the Versions on subprojects
341 def rolled_up_versions
341 def rolled_up_versions
342 @rolled_up_versions ||=
342 @rolled_up_versions ||=
343 Version.scoped(:include => :project,
343 Version.scoped(:include => :project,
344 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
344 :conditions => ["#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status = #{STATUS_ACTIVE}", lft, rgt])
345 end
345 end
346
346
347 # Returns a scope of the Versions used by the project
347 # Returns a scope of the Versions used by the project
348 def shared_versions
348 def shared_versions
349 @shared_versions ||=
349 @shared_versions ||=
350 Version.scoped(:include => :project,
350 Version.scoped(:include => :project,
351 :conditions => "#{Project.table_name}.id = #{id}" +
351 :conditions => "#{Project.table_name}.id = #{id}" +
352 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
352 " OR (#{Project.table_name}.status = #{Project::STATUS_ACTIVE} AND (" +
353 " #{Version.table_name}.sharing = 'system'" +
353 " #{Version.table_name}.sharing = 'system'" +
354 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
354 " OR (#{Project.table_name}.lft >= #{root.lft} AND #{Project.table_name}.rgt <= #{root.rgt} AND #{Version.table_name}.sharing = 'tree')" +
355 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
355 " OR (#{Project.table_name}.lft < #{lft} AND #{Project.table_name}.rgt > #{rgt} AND #{Version.table_name}.sharing IN ('hierarchy', 'descendants'))" +
356 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
356 " OR (#{Project.table_name}.lft > #{lft} AND #{Project.table_name}.rgt < #{rgt} AND #{Version.table_name}.sharing = 'hierarchy')" +
357 "))")
357 "))")
358 end
358 end
359
359
360 # Returns a hash of project users grouped by role
360 # Returns a hash of project users grouped by role
361 def users_by_role
361 def users_by_role
362 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
362 members.find(:all, :include => [:user, :roles]).inject({}) do |h, m|
363 m.roles.each do |r|
363 m.roles.each do |r|
364 h[r] ||= []
364 h[r] ||= []
365 h[r] << m.user
365 h[r] << m.user
366 end
366 end
367 h
367 h
368 end
368 end
369 end
369 end
370
370
371 # Deletes all project's members
371 # Deletes all project's members
372 def delete_all_members
372 def delete_all_members
373 me, mr = Member.table_name, MemberRole.table_name
373 me, mr = Member.table_name, MemberRole.table_name
374 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
374 connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})")
375 Member.delete_all(['project_id = ?', id])
375 Member.delete_all(['project_id = ?', id])
376 end
376 end
377
377
378 # Users issues can be assigned to
378 # Users issues can be assigned to
379 def assignable_users
379 def assignable_users
380 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
380 members.select {|m| m.roles.detect {|role| role.assignable?}}.collect {|m| m.user}.sort
381 end
381 end
382
382
383 # Returns the mail adresses of users that should be always notified on project events
383 # Returns the mail adresses of users that should be always notified on project events
384 def recipients
384 def recipients
385 members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user.mail}
385 notified_users.collect {|user| user.mail}
386 end
386 end
387
387
388 # Returns the users that should be notified on project events
388 # Returns the users that should be notified on project events
389 def notified_users
389 def notified_users
390 members.select {|m| m.mail_notification? || m.user.mail_notification?}.collect {|m| m.user}
390 # TODO: User part should be extracted to User#notify_about?
391 members.select {|m| m.mail_notification? || m.user.mail_notification == 'all'}.collect {|m| m.user}
391 end
392 end
392
393
393 # Returns an array of all custom fields enabled for project issues
394 # Returns an array of all custom fields enabled for project issues
394 # (explictly associated custom fields and custom fields enabled for all projects)
395 # (explictly associated custom fields and custom fields enabled for all projects)
395 def all_issue_custom_fields
396 def all_issue_custom_fields
396 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
397 @all_issue_custom_fields ||= (IssueCustomField.for_all + issue_custom_fields).uniq.sort
397 end
398 end
398
399
399 def project
400 def project
400 self
401 self
401 end
402 end
402
403
403 def <=>(project)
404 def <=>(project)
404 name.downcase <=> project.name.downcase
405 name.downcase <=> project.name.downcase
405 end
406 end
406
407
407 def to_s
408 def to_s
408 name
409 name
409 end
410 end
410
411
411 # Returns a short description of the projects (first lines)
412 # Returns a short description of the projects (first lines)
412 def short_description(length = 255)
413 def short_description(length = 255)
413 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
414 description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
414 end
415 end
415
416
416 def css_classes
417 def css_classes
417 s = 'project'
418 s = 'project'
418 s << ' root' if root?
419 s << ' root' if root?
419 s << ' child' if child?
420 s << ' child' if child?
420 s << (leaf? ? ' leaf' : ' parent')
421 s << (leaf? ? ' leaf' : ' parent')
421 s
422 s
422 end
423 end
423
424
424 # The earliest start date of a project, based on it's issues and versions
425 # The earliest start date of a project, based on it's issues and versions
425 def start_date
426 def start_date
426 if module_enabled?(:issue_tracking)
427 if module_enabled?(:issue_tracking)
427 [
428 [
428 issues.minimum('start_date'),
429 issues.minimum('start_date'),
429 shared_versions.collect(&:effective_date),
430 shared_versions.collect(&:effective_date),
430 shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
431 shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
431 ].flatten.compact.min
432 ].flatten.compact.min
432 end
433 end
433 end
434 end
434
435
435 # The latest due date of an issue or version
436 # The latest due date of an issue or version
436 def due_date
437 def due_date
437 if module_enabled?(:issue_tracking)
438 if module_enabled?(:issue_tracking)
438 [
439 [
439 issues.maximum('due_date'),
440 issues.maximum('due_date'),
440 shared_versions.collect(&:effective_date),
441 shared_versions.collect(&:effective_date),
441 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
442 shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
442 ].flatten.compact.max
443 ].flatten.compact.max
443 end
444 end
444 end
445 end
445
446
446 def overdue?
447 def overdue?
447 active? && !due_date.nil? && (due_date < Date.today)
448 active? && !due_date.nil? && (due_date < Date.today)
448 end
449 end
449
450
450 # Returns the percent completed for this project, based on the
451 # Returns the percent completed for this project, based on the
451 # progress on it's versions.
452 # progress on it's versions.
452 def completed_percent(options={:include_subprojects => false})
453 def completed_percent(options={:include_subprojects => false})
453 if options.delete(:include_subprojects)
454 if options.delete(:include_subprojects)
454 total = self_and_descendants.collect(&:completed_percent).sum
455 total = self_and_descendants.collect(&:completed_percent).sum
455
456
456 total / self_and_descendants.count
457 total / self_and_descendants.count
457 else
458 else
458 if versions.count > 0
459 if versions.count > 0
459 total = versions.collect(&:completed_pourcent).sum
460 total = versions.collect(&:completed_pourcent).sum
460
461
461 total / versions.count
462 total / versions.count
462 else
463 else
463 100
464 100
464 end
465 end
465 end
466 end
466 end
467 end
467
468
468 # Return true if this project is allowed to do the specified action.
469 # Return true if this project is allowed to do the specified action.
469 # action can be:
470 # action can be:
470 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
471 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
471 # * a permission Symbol (eg. :edit_project)
472 # * a permission Symbol (eg. :edit_project)
472 def allows_to?(action)
473 def allows_to?(action)
473 if action.is_a? Hash
474 if action.is_a? Hash
474 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
475 allowed_actions.include? "#{action[:controller]}/#{action[:action]}"
475 else
476 else
476 allowed_permissions.include? action
477 allowed_permissions.include? action
477 end
478 end
478 end
479 end
479
480
480 def module_enabled?(module_name)
481 def module_enabled?(module_name)
481 module_name = module_name.to_s
482 module_name = module_name.to_s
482 enabled_modules.detect {|m| m.name == module_name}
483 enabled_modules.detect {|m| m.name == module_name}
483 end
484 end
484
485
485 def enabled_module_names=(module_names)
486 def enabled_module_names=(module_names)
486 if module_names && module_names.is_a?(Array)
487 if module_names && module_names.is_a?(Array)
487 module_names = module_names.collect(&:to_s)
488 module_names = module_names.collect(&:to_s)
488 # remove disabled modules
489 # remove disabled modules
489 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
490 enabled_modules.each {|mod| mod.destroy unless module_names.include?(mod.name)}
490 # add new modules
491 # add new modules
491 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
492 module_names.reject {|name| module_enabled?(name)}.each {|name| enabled_modules << EnabledModule.new(:name => name)}
492 else
493 else
493 enabled_modules.clear
494 enabled_modules.clear
494 end
495 end
495 end
496 end
496
497
497 # Returns an array of projects that are in this project's hierarchy
498 # Returns an array of projects that are in this project's hierarchy
498 #
499 #
499 # Example: parents, children, siblings
500 # Example: parents, children, siblings
500 def hierarchy
501 def hierarchy
501 parents = project.self_and_ancestors || []
502 parents = project.self_and_ancestors || []
502 descendants = project.descendants || []
503 descendants = project.descendants || []
503 project_hierarchy = parents | descendants # Set union
504 project_hierarchy = parents | descendants # Set union
504 end
505 end
505
506
506 # Returns an auto-generated project identifier based on the last identifier used
507 # Returns an auto-generated project identifier based on the last identifier used
507 def self.next_identifier
508 def self.next_identifier
508 p = Project.find(:first, :order => 'created_on DESC')
509 p = Project.find(:first, :order => 'created_on DESC')
509 p.nil? ? nil : p.identifier.to_s.succ
510 p.nil? ? nil : p.identifier.to_s.succ
510 end
511 end
511
512
512 # Copies and saves the Project instance based on the +project+.
513 # Copies and saves the Project instance based on the +project+.
513 # Duplicates the source project's:
514 # Duplicates the source project's:
514 # * Wiki
515 # * Wiki
515 # * Versions
516 # * Versions
516 # * Categories
517 # * Categories
517 # * Issues
518 # * Issues
518 # * Members
519 # * Members
519 # * Queries
520 # * Queries
520 #
521 #
521 # Accepts an +options+ argument to specify what to copy
522 # Accepts an +options+ argument to specify what to copy
522 #
523 #
523 # Examples:
524 # Examples:
524 # project.copy(1) # => copies everything
525 # project.copy(1) # => copies everything
525 # project.copy(1, :only => 'members') # => copies members only
526 # project.copy(1, :only => 'members') # => copies members only
526 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
527 # project.copy(1, :only => ['members', 'versions']) # => copies members and versions
527 def copy(project, options={})
528 def copy(project, options={})
528 project = project.is_a?(Project) ? project : Project.find(project)
529 project = project.is_a?(Project) ? project : Project.find(project)
529
530
530 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
531 to_be_copied = %w(wiki versions issue_categories issues members queries boards)
531 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
532 to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil?
532
533
533 Project.transaction do
534 Project.transaction do
534 if save
535 if save
535 reload
536 reload
536 to_be_copied.each do |name|
537 to_be_copied.each do |name|
537 send "copy_#{name}", project
538 send "copy_#{name}", project
538 end
539 end
539 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
540 Redmine::Hook.call_hook(:model_project_copy_before_save, :source_project => project, :destination_project => self)
540 save
541 save
541 end
542 end
542 end
543 end
543 end
544 end
544
545
545
546
546 # Copies +project+ and returns the new instance. This will not save
547 # Copies +project+ and returns the new instance. This will not save
547 # the copy
548 # the copy
548 def self.copy_from(project)
549 def self.copy_from(project)
549 begin
550 begin
550 project = project.is_a?(Project) ? project : Project.find(project)
551 project = project.is_a?(Project) ? project : Project.find(project)
551 if project
552 if project
552 # clear unique attributes
553 # clear unique attributes
553 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
554 attributes = project.attributes.dup.except('id', 'name', 'identifier', 'status', 'parent_id', 'lft', 'rgt')
554 copy = Project.new(attributes)
555 copy = Project.new(attributes)
555 copy.enabled_modules = project.enabled_modules
556 copy.enabled_modules = project.enabled_modules
556 copy.trackers = project.trackers
557 copy.trackers = project.trackers
557 copy.custom_values = project.custom_values.collect {|v| v.clone}
558 copy.custom_values = project.custom_values.collect {|v| v.clone}
558 copy.issue_custom_fields = project.issue_custom_fields
559 copy.issue_custom_fields = project.issue_custom_fields
559 return copy
560 return copy
560 else
561 else
561 return nil
562 return nil
562 end
563 end
563 rescue ActiveRecord::RecordNotFound
564 rescue ActiveRecord::RecordNotFound
564 return nil
565 return nil
565 end
566 end
566 end
567 end
567
568
568 private
569 private
569
570
570 # Destroys children before destroying self
571 # Destroys children before destroying self
571 def destroy_children
572 def destroy_children
572 children.each do |child|
573 children.each do |child|
573 child.destroy
574 child.destroy
574 end
575 end
575 end
576 end
576
577
577 # Copies wiki from +project+
578 # Copies wiki from +project+
578 def copy_wiki(project)
579 def copy_wiki(project)
579 # Check that the source project has a wiki first
580 # Check that the source project has a wiki first
580 unless project.wiki.nil?
581 unless project.wiki.nil?
581 self.wiki ||= Wiki.new
582 self.wiki ||= Wiki.new
582 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
583 wiki.attributes = project.wiki.attributes.dup.except("id", "project_id")
583 wiki_pages_map = {}
584 wiki_pages_map = {}
584 project.wiki.pages.each do |page|
585 project.wiki.pages.each do |page|
585 # Skip pages without content
586 # Skip pages without content
586 next if page.content.nil?
587 next if page.content.nil?
587 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
588 new_wiki_content = WikiContent.new(page.content.attributes.dup.except("id", "page_id", "updated_on"))
588 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
589 new_wiki_page = WikiPage.new(page.attributes.dup.except("id", "wiki_id", "created_on", "parent_id"))
589 new_wiki_page.content = new_wiki_content
590 new_wiki_page.content = new_wiki_content
590 wiki.pages << new_wiki_page
591 wiki.pages << new_wiki_page
591 wiki_pages_map[page.id] = new_wiki_page
592 wiki_pages_map[page.id] = new_wiki_page
592 end
593 end
593 wiki.save
594 wiki.save
594 # Reproduce page hierarchy
595 # Reproduce page hierarchy
595 project.wiki.pages.each do |page|
596 project.wiki.pages.each do |page|
596 if page.parent_id && wiki_pages_map[page.id]
597 if page.parent_id && wiki_pages_map[page.id]
597 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
598 wiki_pages_map[page.id].parent = wiki_pages_map[page.parent_id]
598 wiki_pages_map[page.id].save
599 wiki_pages_map[page.id].save
599 end
600 end
600 end
601 end
601 end
602 end
602 end
603 end
603
604
604 # Copies versions from +project+
605 # Copies versions from +project+
605 def copy_versions(project)
606 def copy_versions(project)
606 project.versions.each do |version|
607 project.versions.each do |version|
607 new_version = Version.new
608 new_version = Version.new
608 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
609 new_version.attributes = version.attributes.dup.except("id", "project_id", "created_on", "updated_on")
609 self.versions << new_version
610 self.versions << new_version
610 end
611 end
611 end
612 end
612
613
613 # Copies issue categories from +project+
614 # Copies issue categories from +project+
614 def copy_issue_categories(project)
615 def copy_issue_categories(project)
615 project.issue_categories.each do |issue_category|
616 project.issue_categories.each do |issue_category|
616 new_issue_category = IssueCategory.new
617 new_issue_category = IssueCategory.new
617 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
618 new_issue_category.attributes = issue_category.attributes.dup.except("id", "project_id")
618 self.issue_categories << new_issue_category
619 self.issue_categories << new_issue_category
619 end
620 end
620 end
621 end
621
622
622 # Copies issues from +project+
623 # Copies issues from +project+
623 def copy_issues(project)
624 def copy_issues(project)
624 # Stores the source issue id as a key and the copied issues as the
625 # Stores the source issue id as a key and the copied issues as the
625 # value. Used to map the two togeather for issue relations.
626 # value. Used to map the two togeather for issue relations.
626 issues_map = {}
627 issues_map = {}
627
628
628 # Get issues sorted by root_id, lft so that parent issues
629 # Get issues sorted by root_id, lft so that parent issues
629 # get copied before their children
630 # get copied before their children
630 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
631 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
631 new_issue = Issue.new
632 new_issue = Issue.new
632 new_issue.copy_from(issue)
633 new_issue.copy_from(issue)
633 new_issue.project = self
634 new_issue.project = self
634 # Reassign fixed_versions by name, since names are unique per
635 # Reassign fixed_versions by name, since names are unique per
635 # project and the versions for self are not yet saved
636 # project and the versions for self are not yet saved
636 if issue.fixed_version
637 if issue.fixed_version
637 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
638 new_issue.fixed_version = self.versions.select {|v| v.name == issue.fixed_version.name}.first
638 end
639 end
639 # Reassign the category by name, since names are unique per
640 # Reassign the category by name, since names are unique per
640 # project and the categories for self are not yet saved
641 # project and the categories for self are not yet saved
641 if issue.category
642 if issue.category
642 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
643 new_issue.category = self.issue_categories.select {|c| c.name == issue.category.name}.first
643 end
644 end
644 # Parent issue
645 # Parent issue
645 if issue.parent_id
646 if issue.parent_id
646 if copied_parent = issues_map[issue.parent_id]
647 if copied_parent = issues_map[issue.parent_id]
647 new_issue.parent_issue_id = copied_parent.id
648 new_issue.parent_issue_id = copied_parent.id
648 end
649 end
649 end
650 end
650
651
651 self.issues << new_issue
652 self.issues << new_issue
652 issues_map[issue.id] = new_issue
653 issues_map[issue.id] = new_issue
653 end
654 end
654
655
655 # Relations after in case issues related each other
656 # Relations after in case issues related each other
656 project.issues.each do |issue|
657 project.issues.each do |issue|
657 new_issue = issues_map[issue.id]
658 new_issue = issues_map[issue.id]
658
659
659 # Relations
660 # Relations
660 issue.relations_from.each do |source_relation|
661 issue.relations_from.each do |source_relation|
661 new_issue_relation = IssueRelation.new
662 new_issue_relation = IssueRelation.new
662 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
663 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
663 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
664 new_issue_relation.issue_to = issues_map[source_relation.issue_to_id]
664 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
665 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
665 new_issue_relation.issue_to = source_relation.issue_to
666 new_issue_relation.issue_to = source_relation.issue_to
666 end
667 end
667 new_issue.relations_from << new_issue_relation
668 new_issue.relations_from << new_issue_relation
668 end
669 end
669
670
670 issue.relations_to.each do |source_relation|
671 issue.relations_to.each do |source_relation|
671 new_issue_relation = IssueRelation.new
672 new_issue_relation = IssueRelation.new
672 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
673 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
673 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
674 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
674 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
675 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
675 new_issue_relation.issue_from = source_relation.issue_from
676 new_issue_relation.issue_from = source_relation.issue_from
676 end
677 end
677 new_issue.relations_to << new_issue_relation
678 new_issue.relations_to << new_issue_relation
678 end
679 end
679 end
680 end
680 end
681 end
681
682
682 # Copies members from +project+
683 # Copies members from +project+
683 def copy_members(project)
684 def copy_members(project)
684 project.memberships.each do |member|
685 project.memberships.each do |member|
685 new_member = Member.new
686 new_member = Member.new
686 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
687 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
687 # only copy non inherited roles
688 # only copy non inherited roles
688 # inherited roles will be added when copying the group membership
689 # inherited roles will be added when copying the group membership
689 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
690 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
690 next if role_ids.empty?
691 next if role_ids.empty?
691 new_member.role_ids = role_ids
692 new_member.role_ids = role_ids
692 new_member.project = self
693 new_member.project = self
693 self.members << new_member
694 self.members << new_member
694 end
695 end
695 end
696 end
696
697
697 # Copies queries from +project+
698 # Copies queries from +project+
698 def copy_queries(project)
699 def copy_queries(project)
699 project.queries.each do |query|
700 project.queries.each do |query|
700 new_query = Query.new
701 new_query = Query.new
701 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
702 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
702 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
703 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
703 new_query.project = self
704 new_query.project = self
704 self.queries << new_query
705 self.queries << new_query
705 end
706 end
706 end
707 end
707
708
708 # Copies boards from +project+
709 # Copies boards from +project+
709 def copy_boards(project)
710 def copy_boards(project)
710 project.boards.each do |board|
711 project.boards.each do |board|
711 new_board = Board.new
712 new_board = Board.new
712 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
713 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
713 new_board.project = self
714 new_board.project = self
714 self.boards << new_board
715 self.boards << new_board
715 end
716 end
716 end
717 end
717
718
718 def allowed_permissions
719 def allowed_permissions
719 @allowed_permissions ||= begin
720 @allowed_permissions ||= begin
720 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
721 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
721 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
722 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
722 end
723 end
723 end
724 end
724
725
725 def allowed_actions
726 def allowed_actions
726 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
727 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
727 end
728 end
728
729
729 # Returns all the active Systemwide and project specific activities
730 # Returns all the active Systemwide and project specific activities
730 def active_activities
731 def active_activities
731 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
732 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
732
733
733 if overridden_activity_ids.empty?
734 if overridden_activity_ids.empty?
734 return TimeEntryActivity.shared.active
735 return TimeEntryActivity.shared.active
735 else
736 else
736 return system_activities_and_project_overrides
737 return system_activities_and_project_overrides
737 end
738 end
738 end
739 end
739
740
740 # Returns all the Systemwide and project specific activities
741 # Returns all the Systemwide and project specific activities
741 # (inactive and active)
742 # (inactive and active)
742 def all_activities
743 def all_activities
743 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
744 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
744
745
745 if overridden_activity_ids.empty?
746 if overridden_activity_ids.empty?
746 return TimeEntryActivity.shared
747 return TimeEntryActivity.shared
747 else
748 else
748 return system_activities_and_project_overrides(true)
749 return system_activities_and_project_overrides(true)
749 end
750 end
750 end
751 end
751
752
752 # Returns the systemwide active activities merged with the project specific overrides
753 # Returns the systemwide active activities merged with the project specific overrides
753 def system_activities_and_project_overrides(include_inactive=false)
754 def system_activities_and_project_overrides(include_inactive=false)
754 if include_inactive
755 if include_inactive
755 return TimeEntryActivity.shared.
756 return TimeEntryActivity.shared.
756 find(:all,
757 find(:all,
757 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
758 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
758 self.time_entry_activities
759 self.time_entry_activities
759 else
760 else
760 return TimeEntryActivity.shared.active.
761 return TimeEntryActivity.shared.active.
761 find(:all,
762 find(:all,
762 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
763 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
763 self.time_entry_activities.active
764 self.time_entry_activities.active
764 end
765 end
765 end
766 end
766
767
767 # Archives subprojects recursively
768 # Archives subprojects recursively
768 def archive!
769 def archive!
769 children.each do |subproject|
770 children.each do |subproject|
770 subproject.send :archive!
771 subproject.send :archive!
771 end
772 end
772 update_attribute :status, STATUS_ARCHIVED
773 update_attribute :status, STATUS_ARCHIVED
773 end
774 end
774 end
775 end
@@ -1,967 +1,1017
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class ProjectTest < ActiveSupport::TestCase
20 class ProjectTest < ActiveSupport::TestCase
21 fixtures :all
21 fixtures :all
22
22
23 def setup
23 def setup
24 @ecookbook = Project.find(1)
24 @ecookbook = Project.find(1)
25 @ecookbook_sub1 = Project.find(3)
25 @ecookbook_sub1 = Project.find(3)
26 User.current = nil
26 User.current = nil
27 end
27 end
28
28
29 should_validate_presence_of :name
29 should_validate_presence_of :name
30 should_validate_presence_of :identifier
30 should_validate_presence_of :identifier
31
31
32 should_validate_uniqueness_of :name
32 should_validate_uniqueness_of :name
33 should_validate_uniqueness_of :identifier
33 should_validate_uniqueness_of :identifier
34
34
35 context "associations" do
35 context "associations" do
36 should_have_many :members
36 should_have_many :members
37 should_have_many :users, :through => :members
37 should_have_many :users, :through => :members
38 should_have_many :member_principals
38 should_have_many :member_principals
39 should_have_many :principals, :through => :member_principals
39 should_have_many :principals, :through => :member_principals
40 should_have_many :enabled_modules
40 should_have_many :enabled_modules
41 should_have_many :issues
41 should_have_many :issues
42 should_have_many :issue_changes, :through => :issues
42 should_have_many :issue_changes, :through => :issues
43 should_have_many :versions
43 should_have_many :versions
44 should_have_many :time_entries
44 should_have_many :time_entries
45 should_have_many :queries
45 should_have_many :queries
46 should_have_many :documents
46 should_have_many :documents
47 should_have_many :news
47 should_have_many :news
48 should_have_many :issue_categories
48 should_have_many :issue_categories
49 should_have_many :boards
49 should_have_many :boards
50 should_have_many :changesets, :through => :repository
50 should_have_many :changesets, :through => :repository
51
51
52 should_have_one :repository
52 should_have_one :repository
53 should_have_one :wiki
53 should_have_one :wiki
54
54
55 should_have_and_belong_to_many :trackers
55 should_have_and_belong_to_many :trackers
56 should_have_and_belong_to_many :issue_custom_fields
56 should_have_and_belong_to_many :issue_custom_fields
57 end
57 end
58
58
59 def test_truth
59 def test_truth
60 assert_kind_of Project, @ecookbook
60 assert_kind_of Project, @ecookbook
61 assert_equal "eCookbook", @ecookbook.name
61 assert_equal "eCookbook", @ecookbook.name
62 end
62 end
63
63
64 def test_update
64 def test_update
65 assert_equal "eCookbook", @ecookbook.name
65 assert_equal "eCookbook", @ecookbook.name
66 @ecookbook.name = "eCook"
66 @ecookbook.name = "eCook"
67 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
67 assert @ecookbook.save, @ecookbook.errors.full_messages.join("; ")
68 @ecookbook.reload
68 @ecookbook.reload
69 assert_equal "eCook", @ecookbook.name
69 assert_equal "eCook", @ecookbook.name
70 end
70 end
71
71
72 def test_validate_identifier
72 def test_validate_identifier
73 to_test = {"abc" => true,
73 to_test = {"abc" => true,
74 "ab12" => true,
74 "ab12" => true,
75 "ab-12" => true,
75 "ab-12" => true,
76 "12" => false,
76 "12" => false,
77 "new" => false}
77 "new" => false}
78
78
79 to_test.each do |identifier, valid|
79 to_test.each do |identifier, valid|
80 p = Project.new
80 p = Project.new
81 p.identifier = identifier
81 p.identifier = identifier
82 p.valid?
82 p.valid?
83 assert_equal valid, p.errors.on('identifier').nil?
83 assert_equal valid, p.errors.on('identifier').nil?
84 end
84 end
85 end
85 end
86
86
87 def test_members_should_be_active_users
87 def test_members_should_be_active_users
88 Project.all.each do |project|
88 Project.all.each do |project|
89 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
89 assert_nil project.members.detect {|m| !(m.user.is_a?(User) && m.user.active?) }
90 end
90 end
91 end
91 end
92
92
93 def test_users_should_be_active_users
93 def test_users_should_be_active_users
94 Project.all.each do |project|
94 Project.all.each do |project|
95 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
95 assert_nil project.users.detect {|u| !(u.is_a?(User) && u.active?) }
96 end
96 end
97 end
97 end
98
98
99 def test_archive
99 def test_archive
100 user = @ecookbook.members.first.user
100 user = @ecookbook.members.first.user
101 @ecookbook.archive
101 @ecookbook.archive
102 @ecookbook.reload
102 @ecookbook.reload
103
103
104 assert !@ecookbook.active?
104 assert !@ecookbook.active?
105 assert !user.projects.include?(@ecookbook)
105 assert !user.projects.include?(@ecookbook)
106 # Subproject are also archived
106 # Subproject are also archived
107 assert !@ecookbook.children.empty?
107 assert !@ecookbook.children.empty?
108 assert @ecookbook.descendants.active.empty?
108 assert @ecookbook.descendants.active.empty?
109 end
109 end
110
110
111 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
111 def test_archive_should_fail_if_versions_are_used_by_non_descendant_projects
112 # Assign an issue of a project to a version of a child project
112 # Assign an issue of a project to a version of a child project
113 Issue.find(4).update_attribute :fixed_version_id, 4
113 Issue.find(4).update_attribute :fixed_version_id, 4
114
114
115 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
115 assert_no_difference "Project.count(:all, :conditions => 'status = #{Project::STATUS_ARCHIVED}')" do
116 assert_equal false, @ecookbook.archive
116 assert_equal false, @ecookbook.archive
117 end
117 end
118 @ecookbook.reload
118 @ecookbook.reload
119 assert @ecookbook.active?
119 assert @ecookbook.active?
120 end
120 end
121
121
122 def test_unarchive
122 def test_unarchive
123 user = @ecookbook.members.first.user
123 user = @ecookbook.members.first.user
124 @ecookbook.archive
124 @ecookbook.archive
125 # A subproject of an archived project can not be unarchived
125 # A subproject of an archived project can not be unarchived
126 assert !@ecookbook_sub1.unarchive
126 assert !@ecookbook_sub1.unarchive
127
127
128 # Unarchive project
128 # Unarchive project
129 assert @ecookbook.unarchive
129 assert @ecookbook.unarchive
130 @ecookbook.reload
130 @ecookbook.reload
131 assert @ecookbook.active?
131 assert @ecookbook.active?
132 assert user.projects.include?(@ecookbook)
132 assert user.projects.include?(@ecookbook)
133 # Subproject can now be unarchived
133 # Subproject can now be unarchived
134 @ecookbook_sub1.reload
134 @ecookbook_sub1.reload
135 assert @ecookbook_sub1.unarchive
135 assert @ecookbook_sub1.unarchive
136 end
136 end
137
137
138 def test_destroy
138 def test_destroy
139 # 2 active members
139 # 2 active members
140 assert_equal 2, @ecookbook.members.size
140 assert_equal 2, @ecookbook.members.size
141 # and 1 is locked
141 # and 1 is locked
142 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
142 assert_equal 3, Member.find(:all, :conditions => ['project_id = ?', @ecookbook.id]).size
143 # some boards
143 # some boards
144 assert @ecookbook.boards.any?
144 assert @ecookbook.boards.any?
145
145
146 @ecookbook.destroy
146 @ecookbook.destroy
147 # make sure that the project non longer exists
147 # make sure that the project non longer exists
148 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
148 assert_raise(ActiveRecord::RecordNotFound) { Project.find(@ecookbook.id) }
149 # make sure related data was removed
149 # make sure related data was removed
150 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
150 assert_nil Member.first(:conditions => {:project_id => @ecookbook.id})
151 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
151 assert_nil Board.first(:conditions => {:project_id => @ecookbook.id})
152 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
152 assert_nil Issue.first(:conditions => {:project_id => @ecookbook.id})
153 end
153 end
154
154
155 def test_move_an_orphan_project_to_a_root_project
155 def test_move_an_orphan_project_to_a_root_project
156 sub = Project.find(2)
156 sub = Project.find(2)
157 sub.set_parent! @ecookbook
157 sub.set_parent! @ecookbook
158 assert_equal @ecookbook.id, sub.parent.id
158 assert_equal @ecookbook.id, sub.parent.id
159 @ecookbook.reload
159 @ecookbook.reload
160 assert_equal 4, @ecookbook.children.size
160 assert_equal 4, @ecookbook.children.size
161 end
161 end
162
162
163 def test_move_an_orphan_project_to_a_subproject
163 def test_move_an_orphan_project_to_a_subproject
164 sub = Project.find(2)
164 sub = Project.find(2)
165 assert sub.set_parent!(@ecookbook_sub1)
165 assert sub.set_parent!(@ecookbook_sub1)
166 end
166 end
167
167
168 def test_move_a_root_project_to_a_project
168 def test_move_a_root_project_to_a_project
169 sub = @ecookbook
169 sub = @ecookbook
170 assert sub.set_parent!(Project.find(2))
170 assert sub.set_parent!(Project.find(2))
171 end
171 end
172
172
173 def test_should_not_move_a_project_to_its_children
173 def test_should_not_move_a_project_to_its_children
174 sub = @ecookbook
174 sub = @ecookbook
175 assert !(sub.set_parent!(Project.find(3)))
175 assert !(sub.set_parent!(Project.find(3)))
176 end
176 end
177
177
178 def test_set_parent_should_add_roots_in_alphabetical_order
178 def test_set_parent_should_add_roots_in_alphabetical_order
179 ProjectCustomField.delete_all
179 ProjectCustomField.delete_all
180 Project.delete_all
180 Project.delete_all
181 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
181 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(nil)
182 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
182 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(nil)
183 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
183 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(nil)
184 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
184 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(nil)
185
185
186 assert_equal 4, Project.count
186 assert_equal 4, Project.count
187 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
187 assert_equal Project.all.sort_by(&:name), Project.all.sort_by(&:lft)
188 end
188 end
189
189
190 def test_set_parent_should_add_children_in_alphabetical_order
190 def test_set_parent_should_add_children_in_alphabetical_order
191 ProjectCustomField.delete_all
191 ProjectCustomField.delete_all
192 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
192 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
193 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
193 Project.create!(:name => 'Project C', :identifier => 'project-c').set_parent!(parent)
194 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
194 Project.create!(:name => 'Project B', :identifier => 'project-b').set_parent!(parent)
195 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
195 Project.create!(:name => 'Project D', :identifier => 'project-d').set_parent!(parent)
196 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
196 Project.create!(:name => 'Project A', :identifier => 'project-a').set_parent!(parent)
197
197
198 parent.reload
198 parent.reload
199 assert_equal 4, parent.children.size
199 assert_equal 4, parent.children.size
200 assert_equal parent.children.sort_by(&:name), parent.children
200 assert_equal parent.children.sort_by(&:name), parent.children
201 end
201 end
202
202
203 def test_rebuild_should_sort_children_alphabetically
203 def test_rebuild_should_sort_children_alphabetically
204 ProjectCustomField.delete_all
204 ProjectCustomField.delete_all
205 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
205 parent = Project.create!(:name => 'Parent', :identifier => 'parent')
206 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
206 Project.create!(:name => 'Project C', :identifier => 'project-c').move_to_child_of(parent)
207 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
207 Project.create!(:name => 'Project B', :identifier => 'project-b').move_to_child_of(parent)
208 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
208 Project.create!(:name => 'Project D', :identifier => 'project-d').move_to_child_of(parent)
209 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
209 Project.create!(:name => 'Project A', :identifier => 'project-a').move_to_child_of(parent)
210
210
211 Project.update_all("lft = NULL, rgt = NULL")
211 Project.update_all("lft = NULL, rgt = NULL")
212 Project.rebuild!
212 Project.rebuild!
213
213
214 parent.reload
214 parent.reload
215 assert_equal 4, parent.children.size
215 assert_equal 4, parent.children.size
216 assert_equal parent.children.sort_by(&:name), parent.children
216 assert_equal parent.children.sort_by(&:name), parent.children
217 end
217 end
218
218
219
219
220 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
220 def test_set_parent_should_update_issue_fixed_version_associations_when_a_fixed_version_is_moved_out_of_the_hierarchy
221 # Parent issue with a hierarchy project's fixed version
221 # Parent issue with a hierarchy project's fixed version
222 parent_issue = Issue.find(1)
222 parent_issue = Issue.find(1)
223 parent_issue.update_attribute(:fixed_version_id, 4)
223 parent_issue.update_attribute(:fixed_version_id, 4)
224 parent_issue.reload
224 parent_issue.reload
225 assert_equal 4, parent_issue.fixed_version_id
225 assert_equal 4, parent_issue.fixed_version_id
226
226
227 # Should keep fixed versions for the issues
227 # Should keep fixed versions for the issues
228 issue_with_local_fixed_version = Issue.find(5)
228 issue_with_local_fixed_version = Issue.find(5)
229 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
229 issue_with_local_fixed_version.update_attribute(:fixed_version_id, 4)
230 issue_with_local_fixed_version.reload
230 issue_with_local_fixed_version.reload
231 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
231 assert_equal 4, issue_with_local_fixed_version.fixed_version_id
232
232
233 # Local issue with hierarchy fixed_version
233 # Local issue with hierarchy fixed_version
234 issue_with_hierarchy_fixed_version = Issue.find(13)
234 issue_with_hierarchy_fixed_version = Issue.find(13)
235 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
235 issue_with_hierarchy_fixed_version.update_attribute(:fixed_version_id, 6)
236 issue_with_hierarchy_fixed_version.reload
236 issue_with_hierarchy_fixed_version.reload
237 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
237 assert_equal 6, issue_with_hierarchy_fixed_version.fixed_version_id
238
238
239 # Move project out of the issue's hierarchy
239 # Move project out of the issue's hierarchy
240 moved_project = Project.find(3)
240 moved_project = Project.find(3)
241 moved_project.set_parent!(Project.find(2))
241 moved_project.set_parent!(Project.find(2))
242 parent_issue.reload
242 parent_issue.reload
243 issue_with_local_fixed_version.reload
243 issue_with_local_fixed_version.reload
244 issue_with_hierarchy_fixed_version.reload
244 issue_with_hierarchy_fixed_version.reload
245
245
246 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
246 assert_equal 4, issue_with_local_fixed_version.fixed_version_id, "Fixed version was not keep on an issue local to the moved project"
247 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
247 assert_equal nil, issue_with_hierarchy_fixed_version.fixed_version_id, "Fixed version is still set after moving the Project out of the hierarchy where the version is defined in"
248 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
248 assert_equal nil, parent_issue.fixed_version_id, "Fixed version is still set after moving the Version out of the hierarchy for the issue."
249 end
249 end
250
250
251 def test_parent
251 def test_parent
252 p = Project.find(6).parent
252 p = Project.find(6).parent
253 assert p.is_a?(Project)
253 assert p.is_a?(Project)
254 assert_equal 5, p.id
254 assert_equal 5, p.id
255 end
255 end
256
256
257 def test_ancestors
257 def test_ancestors
258 a = Project.find(6).ancestors
258 a = Project.find(6).ancestors
259 assert a.first.is_a?(Project)
259 assert a.first.is_a?(Project)
260 assert_equal [1, 5], a.collect(&:id)
260 assert_equal [1, 5], a.collect(&:id)
261 end
261 end
262
262
263 def test_root
263 def test_root
264 r = Project.find(6).root
264 r = Project.find(6).root
265 assert r.is_a?(Project)
265 assert r.is_a?(Project)
266 assert_equal 1, r.id
266 assert_equal 1, r.id
267 end
267 end
268
268
269 def test_children
269 def test_children
270 c = Project.find(1).children
270 c = Project.find(1).children
271 assert c.first.is_a?(Project)
271 assert c.first.is_a?(Project)
272 assert_equal [5, 3, 4], c.collect(&:id)
272 assert_equal [5, 3, 4], c.collect(&:id)
273 end
273 end
274
274
275 def test_descendants
275 def test_descendants
276 d = Project.find(1).descendants
276 d = Project.find(1).descendants
277 assert d.first.is_a?(Project)
277 assert d.first.is_a?(Project)
278 assert_equal [5, 6, 3, 4], d.collect(&:id)
278 assert_equal [5, 6, 3, 4], d.collect(&:id)
279 end
279 end
280
280
281 def test_allowed_parents_should_be_empty_for_non_member_user
281 def test_allowed_parents_should_be_empty_for_non_member_user
282 Role.non_member.add_permission!(:add_project)
282 Role.non_member.add_permission!(:add_project)
283 user = User.find(9)
283 user = User.find(9)
284 assert user.memberships.empty?
284 assert user.memberships.empty?
285 User.current = user
285 User.current = user
286 assert Project.new.allowed_parents.compact.empty?
286 assert Project.new.allowed_parents.compact.empty?
287 end
287 end
288
288
289 def test_allowed_parents_with_add_subprojects_permission
289 def test_allowed_parents_with_add_subprojects_permission
290 Role.find(1).remove_permission!(:add_project)
290 Role.find(1).remove_permission!(:add_project)
291 Role.find(1).add_permission!(:add_subprojects)
291 Role.find(1).add_permission!(:add_subprojects)
292 User.current = User.find(2)
292 User.current = User.find(2)
293 # new project
293 # new project
294 assert !Project.new.allowed_parents.include?(nil)
294 assert !Project.new.allowed_parents.include?(nil)
295 assert Project.new.allowed_parents.include?(Project.find(1))
295 assert Project.new.allowed_parents.include?(Project.find(1))
296 # existing root project
296 # existing root project
297 assert Project.find(1).allowed_parents.include?(nil)
297 assert Project.find(1).allowed_parents.include?(nil)
298 # existing child
298 # existing child
299 assert Project.find(3).allowed_parents.include?(Project.find(1))
299 assert Project.find(3).allowed_parents.include?(Project.find(1))
300 assert !Project.find(3).allowed_parents.include?(nil)
300 assert !Project.find(3).allowed_parents.include?(nil)
301 end
301 end
302
302
303 def test_allowed_parents_with_add_project_permission
303 def test_allowed_parents_with_add_project_permission
304 Role.find(1).add_permission!(:add_project)
304 Role.find(1).add_permission!(:add_project)
305 Role.find(1).remove_permission!(:add_subprojects)
305 Role.find(1).remove_permission!(:add_subprojects)
306 User.current = User.find(2)
306 User.current = User.find(2)
307 # new project
307 # new project
308 assert Project.new.allowed_parents.include?(nil)
308 assert Project.new.allowed_parents.include?(nil)
309 assert !Project.new.allowed_parents.include?(Project.find(1))
309 assert !Project.new.allowed_parents.include?(Project.find(1))
310 # existing root project
310 # existing root project
311 assert Project.find(1).allowed_parents.include?(nil)
311 assert Project.find(1).allowed_parents.include?(nil)
312 # existing child
312 # existing child
313 assert Project.find(3).allowed_parents.include?(Project.find(1))
313 assert Project.find(3).allowed_parents.include?(Project.find(1))
314 assert Project.find(3).allowed_parents.include?(nil)
314 assert Project.find(3).allowed_parents.include?(nil)
315 end
315 end
316
316
317 def test_allowed_parents_with_add_project_and_subprojects_permission
317 def test_allowed_parents_with_add_project_and_subprojects_permission
318 Role.find(1).add_permission!(:add_project)
318 Role.find(1).add_permission!(:add_project)
319 Role.find(1).add_permission!(:add_subprojects)
319 Role.find(1).add_permission!(:add_subprojects)
320 User.current = User.find(2)
320 User.current = User.find(2)
321 # new project
321 # new project
322 assert Project.new.allowed_parents.include?(nil)
322 assert Project.new.allowed_parents.include?(nil)
323 assert Project.new.allowed_parents.include?(Project.find(1))
323 assert Project.new.allowed_parents.include?(Project.find(1))
324 # existing root project
324 # existing root project
325 assert Project.find(1).allowed_parents.include?(nil)
325 assert Project.find(1).allowed_parents.include?(nil)
326 # existing child
326 # existing child
327 assert Project.find(3).allowed_parents.include?(Project.find(1))
327 assert Project.find(3).allowed_parents.include?(Project.find(1))
328 assert Project.find(3).allowed_parents.include?(nil)
328 assert Project.find(3).allowed_parents.include?(nil)
329 end
329 end
330
330
331 def test_users_by_role
331 def test_users_by_role
332 users_by_role = Project.find(1).users_by_role
332 users_by_role = Project.find(1).users_by_role
333 assert_kind_of Hash, users_by_role
333 assert_kind_of Hash, users_by_role
334 role = Role.find(1)
334 role = Role.find(1)
335 assert_kind_of Array, users_by_role[role]
335 assert_kind_of Array, users_by_role[role]
336 assert users_by_role[role].include?(User.find(2))
336 assert users_by_role[role].include?(User.find(2))
337 end
337 end
338
338
339 def test_rolled_up_trackers
339 def test_rolled_up_trackers
340 parent = Project.find(1)
340 parent = Project.find(1)
341 parent.trackers = Tracker.find([1,2])
341 parent.trackers = Tracker.find([1,2])
342 child = parent.children.find(3)
342 child = parent.children.find(3)
343
343
344 assert_equal [1, 2], parent.tracker_ids
344 assert_equal [1, 2], parent.tracker_ids
345 assert_equal [2, 3], child.trackers.collect(&:id)
345 assert_equal [2, 3], child.trackers.collect(&:id)
346
346
347 assert_kind_of Tracker, parent.rolled_up_trackers.first
347 assert_kind_of Tracker, parent.rolled_up_trackers.first
348 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
348 assert_equal Tracker.find(1), parent.rolled_up_trackers.first
349
349
350 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
350 assert_equal [1, 2, 3], parent.rolled_up_trackers.collect(&:id)
351 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
351 assert_equal [2, 3], child.rolled_up_trackers.collect(&:id)
352 end
352 end
353
353
354 def test_rolled_up_trackers_should_ignore_archived_subprojects
354 def test_rolled_up_trackers_should_ignore_archived_subprojects
355 parent = Project.find(1)
355 parent = Project.find(1)
356 parent.trackers = Tracker.find([1,2])
356 parent.trackers = Tracker.find([1,2])
357 child = parent.children.find(3)
357 child = parent.children.find(3)
358 child.trackers = Tracker.find([1,3])
358 child.trackers = Tracker.find([1,3])
359 parent.children.each(&:archive)
359 parent.children.each(&:archive)
360
360
361 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
361 assert_equal [1,2], parent.rolled_up_trackers.collect(&:id)
362 end
362 end
363
363
364 context "#rolled_up_versions" do
364 context "#rolled_up_versions" do
365 setup do
365 setup do
366 @project = Project.generate!
366 @project = Project.generate!
367 @parent_version_1 = Version.generate!(:project => @project)
367 @parent_version_1 = Version.generate!(:project => @project)
368 @parent_version_2 = Version.generate!(:project => @project)
368 @parent_version_2 = Version.generate!(:project => @project)
369 end
369 end
370
370
371 should "include the versions for the current project" do
371 should "include the versions for the current project" do
372 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
372 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
373 end
373 end
374
374
375 should "include versions for a subproject" do
375 should "include versions for a subproject" do
376 @subproject = Project.generate!
376 @subproject = Project.generate!
377 @subproject.set_parent!(@project)
377 @subproject.set_parent!(@project)
378 @subproject_version = Version.generate!(:project => @subproject)
378 @subproject_version = Version.generate!(:project => @subproject)
379
379
380 assert_same_elements [
380 assert_same_elements [
381 @parent_version_1,
381 @parent_version_1,
382 @parent_version_2,
382 @parent_version_2,
383 @subproject_version
383 @subproject_version
384 ], @project.rolled_up_versions
384 ], @project.rolled_up_versions
385 end
385 end
386
386
387 should "include versions for a sub-subproject" do
387 should "include versions for a sub-subproject" do
388 @subproject = Project.generate!
388 @subproject = Project.generate!
389 @subproject.set_parent!(@project)
389 @subproject.set_parent!(@project)
390 @sub_subproject = Project.generate!
390 @sub_subproject = Project.generate!
391 @sub_subproject.set_parent!(@subproject)
391 @sub_subproject.set_parent!(@subproject)
392 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
392 @sub_subproject_version = Version.generate!(:project => @sub_subproject)
393
393
394 @project.reload
394 @project.reload
395
395
396 assert_same_elements [
396 assert_same_elements [
397 @parent_version_1,
397 @parent_version_1,
398 @parent_version_2,
398 @parent_version_2,
399 @sub_subproject_version
399 @sub_subproject_version
400 ], @project.rolled_up_versions
400 ], @project.rolled_up_versions
401 end
401 end
402
402
403
403
404 should "only check active projects" do
404 should "only check active projects" do
405 @subproject = Project.generate!
405 @subproject = Project.generate!
406 @subproject.set_parent!(@project)
406 @subproject.set_parent!(@project)
407 @subproject_version = Version.generate!(:project => @subproject)
407 @subproject_version = Version.generate!(:project => @subproject)
408 assert @subproject.archive
408 assert @subproject.archive
409
409
410 @project.reload
410 @project.reload
411
411
412 assert !@subproject.active?
412 assert !@subproject.active?
413 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
413 assert_same_elements [@parent_version_1, @parent_version_2], @project.rolled_up_versions
414 end
414 end
415 end
415 end
416
416
417 def test_shared_versions_none_sharing
417 def test_shared_versions_none_sharing
418 p = Project.find(5)
418 p = Project.find(5)
419 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
419 v = Version.create!(:name => 'none_sharing', :project => p, :sharing => 'none')
420 assert p.shared_versions.include?(v)
420 assert p.shared_versions.include?(v)
421 assert !p.children.first.shared_versions.include?(v)
421 assert !p.children.first.shared_versions.include?(v)
422 assert !p.root.shared_versions.include?(v)
422 assert !p.root.shared_versions.include?(v)
423 assert !p.siblings.first.shared_versions.include?(v)
423 assert !p.siblings.first.shared_versions.include?(v)
424 assert !p.root.siblings.first.shared_versions.include?(v)
424 assert !p.root.siblings.first.shared_versions.include?(v)
425 end
425 end
426
426
427 def test_shared_versions_descendants_sharing
427 def test_shared_versions_descendants_sharing
428 p = Project.find(5)
428 p = Project.find(5)
429 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
429 v = Version.create!(:name => 'descendants_sharing', :project => p, :sharing => 'descendants')
430 assert p.shared_versions.include?(v)
430 assert p.shared_versions.include?(v)
431 assert p.children.first.shared_versions.include?(v)
431 assert p.children.first.shared_versions.include?(v)
432 assert !p.root.shared_versions.include?(v)
432 assert !p.root.shared_versions.include?(v)
433 assert !p.siblings.first.shared_versions.include?(v)
433 assert !p.siblings.first.shared_versions.include?(v)
434 assert !p.root.siblings.first.shared_versions.include?(v)
434 assert !p.root.siblings.first.shared_versions.include?(v)
435 end
435 end
436
436
437 def test_shared_versions_hierarchy_sharing
437 def test_shared_versions_hierarchy_sharing
438 p = Project.find(5)
438 p = Project.find(5)
439 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
439 v = Version.create!(:name => 'hierarchy_sharing', :project => p, :sharing => 'hierarchy')
440 assert p.shared_versions.include?(v)
440 assert p.shared_versions.include?(v)
441 assert p.children.first.shared_versions.include?(v)
441 assert p.children.first.shared_versions.include?(v)
442 assert p.root.shared_versions.include?(v)
442 assert p.root.shared_versions.include?(v)
443 assert !p.siblings.first.shared_versions.include?(v)
443 assert !p.siblings.first.shared_versions.include?(v)
444 assert !p.root.siblings.first.shared_versions.include?(v)
444 assert !p.root.siblings.first.shared_versions.include?(v)
445 end
445 end
446
446
447 def test_shared_versions_tree_sharing
447 def test_shared_versions_tree_sharing
448 p = Project.find(5)
448 p = Project.find(5)
449 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
449 v = Version.create!(:name => 'tree_sharing', :project => p, :sharing => 'tree')
450 assert p.shared_versions.include?(v)
450 assert p.shared_versions.include?(v)
451 assert p.children.first.shared_versions.include?(v)
451 assert p.children.first.shared_versions.include?(v)
452 assert p.root.shared_versions.include?(v)
452 assert p.root.shared_versions.include?(v)
453 assert p.siblings.first.shared_versions.include?(v)
453 assert p.siblings.first.shared_versions.include?(v)
454 assert !p.root.siblings.first.shared_versions.include?(v)
454 assert !p.root.siblings.first.shared_versions.include?(v)
455 end
455 end
456
456
457 def test_shared_versions_system_sharing
457 def test_shared_versions_system_sharing
458 p = Project.find(5)
458 p = Project.find(5)
459 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
459 v = Version.create!(:name => 'system_sharing', :project => p, :sharing => 'system')
460 assert p.shared_versions.include?(v)
460 assert p.shared_versions.include?(v)
461 assert p.children.first.shared_versions.include?(v)
461 assert p.children.first.shared_versions.include?(v)
462 assert p.root.shared_versions.include?(v)
462 assert p.root.shared_versions.include?(v)
463 assert p.siblings.first.shared_versions.include?(v)
463 assert p.siblings.first.shared_versions.include?(v)
464 assert p.root.siblings.first.shared_versions.include?(v)
464 assert p.root.siblings.first.shared_versions.include?(v)
465 end
465 end
466
466
467 def test_shared_versions
467 def test_shared_versions
468 parent = Project.find(1)
468 parent = Project.find(1)
469 child = parent.children.find(3)
469 child = parent.children.find(3)
470 private_child = parent.children.find(5)
470 private_child = parent.children.find(5)
471
471
472 assert_equal [1,2,3], parent.version_ids.sort
472 assert_equal [1,2,3], parent.version_ids.sort
473 assert_equal [4], child.version_ids
473 assert_equal [4], child.version_ids
474 assert_equal [6], private_child.version_ids
474 assert_equal [6], private_child.version_ids
475 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
475 assert_equal [7], Version.find_all_by_sharing('system').collect(&:id)
476
476
477 assert_equal 6, parent.shared_versions.size
477 assert_equal 6, parent.shared_versions.size
478 parent.shared_versions.each do |version|
478 parent.shared_versions.each do |version|
479 assert_kind_of Version, version
479 assert_kind_of Version, version
480 end
480 end
481
481
482 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
482 assert_equal [1,2,3,4,6,7], parent.shared_versions.collect(&:id).sort
483 end
483 end
484
484
485 def test_shared_versions_should_ignore_archived_subprojects
485 def test_shared_versions_should_ignore_archived_subprojects
486 parent = Project.find(1)
486 parent = Project.find(1)
487 child = parent.children.find(3)
487 child = parent.children.find(3)
488 child.archive
488 child.archive
489 parent.reload
489 parent.reload
490
490
491 assert_equal [1,2,3], parent.version_ids.sort
491 assert_equal [1,2,3], parent.version_ids.sort
492 assert_equal [4], child.version_ids
492 assert_equal [4], child.version_ids
493 assert !parent.shared_versions.collect(&:id).include?(4)
493 assert !parent.shared_versions.collect(&:id).include?(4)
494 end
494 end
495
495
496 def test_shared_versions_visible_to_user
496 def test_shared_versions_visible_to_user
497 user = User.find(3)
497 user = User.find(3)
498 parent = Project.find(1)
498 parent = Project.find(1)
499 child = parent.children.find(5)
499 child = parent.children.find(5)
500
500
501 assert_equal [1,2,3], parent.version_ids.sort
501 assert_equal [1,2,3], parent.version_ids.sort
502 assert_equal [6], child.version_ids
502 assert_equal [6], child.version_ids
503
503
504 versions = parent.shared_versions.visible(user)
504 versions = parent.shared_versions.visible(user)
505
505
506 assert_equal 4, versions.size
506 assert_equal 4, versions.size
507 versions.each do |version|
507 versions.each do |version|
508 assert_kind_of Version, version
508 assert_kind_of Version, version
509 end
509 end
510
510
511 assert !versions.collect(&:id).include?(6)
511 assert !versions.collect(&:id).include?(6)
512 end
512 end
513
513
514
514
515 def test_next_identifier
515 def test_next_identifier
516 ProjectCustomField.delete_all
516 ProjectCustomField.delete_all
517 Project.create!(:name => 'last', :identifier => 'p2008040')
517 Project.create!(:name => 'last', :identifier => 'p2008040')
518 assert_equal 'p2008041', Project.next_identifier
518 assert_equal 'p2008041', Project.next_identifier
519 end
519 end
520
520
521 def test_next_identifier_first_project
521 def test_next_identifier_first_project
522 Project.delete_all
522 Project.delete_all
523 assert_nil Project.next_identifier
523 assert_nil Project.next_identifier
524 end
524 end
525
525
526
526
527 def test_enabled_module_names_should_not_recreate_enabled_modules
527 def test_enabled_module_names_should_not_recreate_enabled_modules
528 project = Project.find(1)
528 project = Project.find(1)
529 # Remove one module
529 # Remove one module
530 modules = project.enabled_modules.slice(0..-2)
530 modules = project.enabled_modules.slice(0..-2)
531 assert modules.any?
531 assert modules.any?
532 assert_difference 'EnabledModule.count', -1 do
532 assert_difference 'EnabledModule.count', -1 do
533 project.enabled_module_names = modules.collect(&:name)
533 project.enabled_module_names = modules.collect(&:name)
534 end
534 end
535 project.reload
535 project.reload
536 # Ids should be preserved
536 # Ids should be preserved
537 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
537 assert_equal project.enabled_module_ids.sort, modules.collect(&:id).sort
538 end
538 end
539
539
540 def test_copy_from_existing_project
540 def test_copy_from_existing_project
541 source_project = Project.find(1)
541 source_project = Project.find(1)
542 copied_project = Project.copy_from(1)
542 copied_project = Project.copy_from(1)
543
543
544 assert copied_project
544 assert copied_project
545 # Cleared attributes
545 # Cleared attributes
546 assert copied_project.id.blank?
546 assert copied_project.id.blank?
547 assert copied_project.name.blank?
547 assert copied_project.name.blank?
548 assert copied_project.identifier.blank?
548 assert copied_project.identifier.blank?
549
549
550 # Duplicated attributes
550 # Duplicated attributes
551 assert_equal source_project.description, copied_project.description
551 assert_equal source_project.description, copied_project.description
552 assert_equal source_project.enabled_modules, copied_project.enabled_modules
552 assert_equal source_project.enabled_modules, copied_project.enabled_modules
553 assert_equal source_project.trackers, copied_project.trackers
553 assert_equal source_project.trackers, copied_project.trackers
554
554
555 # Default attributes
555 # Default attributes
556 assert_equal 1, copied_project.status
556 assert_equal 1, copied_project.status
557 end
557 end
558
558
559 def test_activities_should_use_the_system_activities
559 def test_activities_should_use_the_system_activities
560 project = Project.find(1)
560 project = Project.find(1)
561 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
561 assert_equal project.activities, TimeEntryActivity.find(:all, :conditions => {:active => true} )
562 end
562 end
563
563
564
564
565 def test_activities_should_use_the_project_specific_activities
565 def test_activities_should_use_the_project_specific_activities
566 project = Project.find(1)
566 project = Project.find(1)
567 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
567 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project})
568 assert overridden_activity.save!
568 assert overridden_activity.save!
569
569
570 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
570 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
571 end
571 end
572
572
573 def test_activities_should_not_include_the_inactive_project_specific_activities
573 def test_activities_should_not_include_the_inactive_project_specific_activities
574 project = Project.find(1)
574 project = Project.find(1)
575 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
575 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
576 assert overridden_activity.save!
576 assert overridden_activity.save!
577
577
578 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
578 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity found"
579 end
579 end
580
580
581 def test_activities_should_not_include_project_specific_activities_from_other_projects
581 def test_activities_should_not_include_project_specific_activities_from_other_projects
582 project = Project.find(1)
582 project = Project.find(1)
583 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
583 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(2)})
584 assert overridden_activity.save!
584 assert overridden_activity.save!
585
585
586 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
586 assert !project.activities.include?(overridden_activity), "Project specific Activity found on a different project"
587 end
587 end
588
588
589 def test_activities_should_handle_nils
589 def test_activities_should_handle_nils
590 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
590 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => Project.find(1), :parent => TimeEntryActivity.find(:first)})
591 TimeEntryActivity.delete_all
591 TimeEntryActivity.delete_all
592
592
593 # No activities
593 # No activities
594 project = Project.find(1)
594 project = Project.find(1)
595 assert project.activities.empty?
595 assert project.activities.empty?
596
596
597 # No system, one overridden
597 # No system, one overridden
598 assert overridden_activity.save!
598 assert overridden_activity.save!
599 project.reload
599 project.reload
600 assert_equal [overridden_activity], project.activities
600 assert_equal [overridden_activity], project.activities
601 end
601 end
602
602
603 def test_activities_should_override_system_activities_with_project_activities
603 def test_activities_should_override_system_activities_with_project_activities
604 project = Project.find(1)
604 project = Project.find(1)
605 parent_activity = TimeEntryActivity.find(:first)
605 parent_activity = TimeEntryActivity.find(:first)
606 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
606 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => parent_activity})
607 assert overridden_activity.save!
607 assert overridden_activity.save!
608
608
609 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
609 assert project.activities.include?(overridden_activity), "Project specific Activity not found"
610 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
610 assert !project.activities.include?(parent_activity), "System Activity found when it should have been overridden"
611 end
611 end
612
612
613 def test_activities_should_include_inactive_activities_if_specified
613 def test_activities_should_include_inactive_activities_if_specified
614 project = Project.find(1)
614 project = Project.find(1)
615 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
615 overridden_activity = TimeEntryActivity.new({:name => "Project", :project => project, :parent => TimeEntryActivity.find(:first), :active => false})
616 assert overridden_activity.save!
616 assert overridden_activity.save!
617
617
618 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
618 assert project.activities(true).include?(overridden_activity), "Inactive Project specific Activity not found"
619 end
619 end
620
620
621 test 'activities should not include active System activities if the project has an override that is inactive' do
621 test 'activities should not include active System activities if the project has an override that is inactive' do
622 project = Project.find(1)
622 project = Project.find(1)
623 system_activity = TimeEntryActivity.find_by_name('Design')
623 system_activity = TimeEntryActivity.find_by_name('Design')
624 assert system_activity.active?
624 assert system_activity.active?
625 overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
625 overridden_activity = TimeEntryActivity.generate!(:project => project, :parent => system_activity, :active => false)
626 assert overridden_activity.save!
626 assert overridden_activity.save!
627
627
628 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
628 assert !project.activities.include?(overridden_activity), "Inactive Project specific Activity not found"
629 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
629 assert !project.activities.include?(system_activity), "System activity found when the project has an inactive override"
630 end
630 end
631
631
632 def test_close_completed_versions
632 def test_close_completed_versions
633 Version.update_all("status = 'open'")
633 Version.update_all("status = 'open'")
634 project = Project.find(1)
634 project = Project.find(1)
635 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
635 assert_not_nil project.versions.detect {|v| v.completed? && v.status == 'open'}
636 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
636 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
637 project.close_completed_versions
637 project.close_completed_versions
638 project.reload
638 project.reload
639 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
639 assert_nil project.versions.detect {|v| v.completed? && v.status != 'closed'}
640 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
640 assert_not_nil project.versions.detect {|v| !v.completed? && v.status == 'open'}
641 end
641 end
642
642
643 context "Project#copy" do
643 context "Project#copy" do
644 setup do
644 setup do
645 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
645 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
646 Project.destroy_all :identifier => "copy-test"
646 Project.destroy_all :identifier => "copy-test"
647 @source_project = Project.find(2)
647 @source_project = Project.find(2)
648 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
648 @project = Project.new(:name => 'Copy Test', :identifier => 'copy-test')
649 @project.trackers = @source_project.trackers
649 @project.trackers = @source_project.trackers
650 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
650 @project.enabled_module_names = @source_project.enabled_modules.collect(&:name)
651 end
651 end
652
652
653 should "copy issues" do
653 should "copy issues" do
654 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
654 @source_project.issues << Issue.generate!(:status => IssueStatus.find_by_name('Closed'),
655 :subject => "copy issue status",
655 :subject => "copy issue status",
656 :tracker_id => 1,
656 :tracker_id => 1,
657 :assigned_to_id => 2,
657 :assigned_to_id => 2,
658 :project_id => @source_project.id)
658 :project_id => @source_project.id)
659 assert @project.valid?
659 assert @project.valid?
660 assert @project.issues.empty?
660 assert @project.issues.empty?
661 assert @project.copy(@source_project)
661 assert @project.copy(@source_project)
662
662
663 assert_equal @source_project.issues.size, @project.issues.size
663 assert_equal @source_project.issues.size, @project.issues.size
664 @project.issues.each do |issue|
664 @project.issues.each do |issue|
665 assert issue.valid?
665 assert issue.valid?
666 assert ! issue.assigned_to.blank?
666 assert ! issue.assigned_to.blank?
667 assert_equal @project, issue.project
667 assert_equal @project, issue.project
668 end
668 end
669
669
670 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
670 copied_issue = @project.issues.first(:conditions => {:subject => "copy issue status"})
671 assert copied_issue
671 assert copied_issue
672 assert copied_issue.status
672 assert copied_issue.status
673 assert_equal "Closed", copied_issue.status.name
673 assert_equal "Closed", copied_issue.status.name
674 end
674 end
675
675
676 should "change the new issues to use the copied version" do
676 should "change the new issues to use the copied version" do
677 User.current = User.find(1)
677 User.current = User.find(1)
678 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
678 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
679 @source_project.versions << assigned_version
679 @source_project.versions << assigned_version
680 assert_equal 3, @source_project.versions.size
680 assert_equal 3, @source_project.versions.size
681 Issue.generate_for_project!(@source_project,
681 Issue.generate_for_project!(@source_project,
682 :fixed_version_id => assigned_version.id,
682 :fixed_version_id => assigned_version.id,
683 :subject => "change the new issues to use the copied version",
683 :subject => "change the new issues to use the copied version",
684 :tracker_id => 1,
684 :tracker_id => 1,
685 :project_id => @source_project.id)
685 :project_id => @source_project.id)
686
686
687 assert @project.copy(@source_project)
687 assert @project.copy(@source_project)
688 @project.reload
688 @project.reload
689 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
689 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
690
690
691 assert copied_issue
691 assert copied_issue
692 assert copied_issue.fixed_version
692 assert copied_issue.fixed_version
693 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
693 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
694 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
694 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
695 end
695 end
696
696
697 should "copy issue relations" do
697 should "copy issue relations" do
698 Setting.cross_project_issue_relations = '1'
698 Setting.cross_project_issue_relations = '1'
699
699
700 second_issue = Issue.generate!(:status_id => 5,
700 second_issue = Issue.generate!(:status_id => 5,
701 :subject => "copy issue relation",
701 :subject => "copy issue relation",
702 :tracker_id => 1,
702 :tracker_id => 1,
703 :assigned_to_id => 2,
703 :assigned_to_id => 2,
704 :project_id => @source_project.id)
704 :project_id => @source_project.id)
705 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
705 source_relation = IssueRelation.generate!(:issue_from => Issue.find(4),
706 :issue_to => second_issue,
706 :issue_to => second_issue,
707 :relation_type => "relates")
707 :relation_type => "relates")
708 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
708 source_relation_cross_project = IssueRelation.generate!(:issue_from => Issue.find(1),
709 :issue_to => second_issue,
709 :issue_to => second_issue,
710 :relation_type => "duplicates")
710 :relation_type => "duplicates")
711
711
712 assert @project.copy(@source_project)
712 assert @project.copy(@source_project)
713 assert_equal @source_project.issues.count, @project.issues.count
713 assert_equal @source_project.issues.count, @project.issues.count
714 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
714 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
715 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
715 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
716
716
717 # First issue with a relation on project
717 # First issue with a relation on project
718 assert_equal 1, copied_issue.relations.size, "Relation not copied"
718 assert_equal 1, copied_issue.relations.size, "Relation not copied"
719 copied_relation = copied_issue.relations.first
719 copied_relation = copied_issue.relations.first
720 assert_equal "relates", copied_relation.relation_type
720 assert_equal "relates", copied_relation.relation_type
721 assert_equal copied_second_issue.id, copied_relation.issue_to_id
721 assert_equal copied_second_issue.id, copied_relation.issue_to_id
722 assert_not_equal source_relation.id, copied_relation.id
722 assert_not_equal source_relation.id, copied_relation.id
723
723
724 # Second issue with a cross project relation
724 # Second issue with a cross project relation
725 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
725 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
726 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
726 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
727 assert_equal "duplicates", copied_relation.relation_type
727 assert_equal "duplicates", copied_relation.relation_type
728 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
728 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
729 assert_not_equal source_relation_cross_project.id, copied_relation.id
729 assert_not_equal source_relation_cross_project.id, copied_relation.id
730 end
730 end
731
731
732 should "copy memberships" do
732 should "copy memberships" do
733 assert @project.valid?
733 assert @project.valid?
734 assert @project.members.empty?
734 assert @project.members.empty?
735 assert @project.copy(@source_project)
735 assert @project.copy(@source_project)
736
736
737 assert_equal @source_project.memberships.size, @project.memberships.size
737 assert_equal @source_project.memberships.size, @project.memberships.size
738 @project.memberships.each do |membership|
738 @project.memberships.each do |membership|
739 assert membership
739 assert membership
740 assert_equal @project, membership.project
740 assert_equal @project, membership.project
741 end
741 end
742 end
742 end
743
743
744 should "copy project specific queries" do
744 should "copy project specific queries" do
745 assert @project.valid?
745 assert @project.valid?
746 assert @project.queries.empty?
746 assert @project.queries.empty?
747 assert @project.copy(@source_project)
747 assert @project.copy(@source_project)
748
748
749 assert_equal @source_project.queries.size, @project.queries.size
749 assert_equal @source_project.queries.size, @project.queries.size
750 @project.queries.each do |query|
750 @project.queries.each do |query|
751 assert query
751 assert query
752 assert_equal @project, query.project
752 assert_equal @project, query.project
753 end
753 end
754 end
754 end
755
755
756 should "copy versions" do
756 should "copy versions" do
757 @source_project.versions << Version.generate!
757 @source_project.versions << Version.generate!
758 @source_project.versions << Version.generate!
758 @source_project.versions << Version.generate!
759
759
760 assert @project.versions.empty?
760 assert @project.versions.empty?
761 assert @project.copy(@source_project)
761 assert @project.copy(@source_project)
762
762
763 assert_equal @source_project.versions.size, @project.versions.size
763 assert_equal @source_project.versions.size, @project.versions.size
764 @project.versions.each do |version|
764 @project.versions.each do |version|
765 assert version
765 assert version
766 assert_equal @project, version.project
766 assert_equal @project, version.project
767 end
767 end
768 end
768 end
769
769
770 should "copy wiki" do
770 should "copy wiki" do
771 assert_difference 'Wiki.count' do
771 assert_difference 'Wiki.count' do
772 assert @project.copy(@source_project)
772 assert @project.copy(@source_project)
773 end
773 end
774
774
775 assert @project.wiki
775 assert @project.wiki
776 assert_not_equal @source_project.wiki, @project.wiki
776 assert_not_equal @source_project.wiki, @project.wiki
777 assert_equal "Start page", @project.wiki.start_page
777 assert_equal "Start page", @project.wiki.start_page
778 end
778 end
779
779
780 should "copy wiki pages and content with hierarchy" do
780 should "copy wiki pages and content with hierarchy" do
781 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
781 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
782 assert @project.copy(@source_project)
782 assert @project.copy(@source_project)
783 end
783 end
784
784
785 assert @project.wiki
785 assert @project.wiki
786 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
786 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
787
787
788 @project.wiki.pages.each do |wiki_page|
788 @project.wiki.pages.each do |wiki_page|
789 assert wiki_page.content
789 assert wiki_page.content
790 assert !@source_project.wiki.pages.include?(wiki_page)
790 assert !@source_project.wiki.pages.include?(wiki_page)
791 end
791 end
792
792
793 parent = @project.wiki.find_page('Parent_page')
793 parent = @project.wiki.find_page('Parent_page')
794 child1 = @project.wiki.find_page('Child_page_1')
794 child1 = @project.wiki.find_page('Child_page_1')
795 child2 = @project.wiki.find_page('Child_page_2')
795 child2 = @project.wiki.find_page('Child_page_2')
796 assert_equal parent, child1.parent
796 assert_equal parent, child1.parent
797 assert_equal parent, child2.parent
797 assert_equal parent, child2.parent
798 end
798 end
799
799
800 should "copy issue categories" do
800 should "copy issue categories" do
801 assert @project.copy(@source_project)
801 assert @project.copy(@source_project)
802
802
803 assert_equal 2, @project.issue_categories.size
803 assert_equal 2, @project.issue_categories.size
804 @project.issue_categories.each do |issue_category|
804 @project.issue_categories.each do |issue_category|
805 assert !@source_project.issue_categories.include?(issue_category)
805 assert !@source_project.issue_categories.include?(issue_category)
806 end
806 end
807 end
807 end
808
808
809 should "copy boards" do
809 should "copy boards" do
810 assert @project.copy(@source_project)
810 assert @project.copy(@source_project)
811
811
812 assert_equal 1, @project.boards.size
812 assert_equal 1, @project.boards.size
813 @project.boards.each do |board|
813 @project.boards.each do |board|
814 assert !@source_project.boards.include?(board)
814 assert !@source_project.boards.include?(board)
815 end
815 end
816 end
816 end
817
817
818 should "change the new issues to use the copied issue categories" do
818 should "change the new issues to use the copied issue categories" do
819 issue = Issue.find(4)
819 issue = Issue.find(4)
820 issue.update_attribute(:category_id, 3)
820 issue.update_attribute(:category_id, 3)
821
821
822 assert @project.copy(@source_project)
822 assert @project.copy(@source_project)
823
823
824 @project.issues.each do |issue|
824 @project.issues.each do |issue|
825 assert issue.category
825 assert issue.category
826 assert_equal "Stock management", issue.category.name # Same name
826 assert_equal "Stock management", issue.category.name # Same name
827 assert_not_equal IssueCategory.find(3), issue.category # Different record
827 assert_not_equal IssueCategory.find(3), issue.category # Different record
828 end
828 end
829 end
829 end
830
830
831 should "limit copy with :only option" do
831 should "limit copy with :only option" do
832 assert @project.members.empty?
832 assert @project.members.empty?
833 assert @project.issue_categories.empty?
833 assert @project.issue_categories.empty?
834 assert @source_project.issues.any?
834 assert @source_project.issues.any?
835
835
836 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
836 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
837
837
838 assert @project.members.any?
838 assert @project.members.any?
839 assert @project.issue_categories.any?
839 assert @project.issue_categories.any?
840 assert @project.issues.empty?
840 assert @project.issues.empty?
841 end
841 end
842
842
843 end
843 end
844
844
845 context "#start_date" do
845 context "#start_date" do
846 setup do
846 setup do
847 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
847 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
848 @project = Project.generate!(:identifier => 'test0')
848 @project = Project.generate!(:identifier => 'test0')
849 @project.trackers << Tracker.generate!
849 @project.trackers << Tracker.generate!
850 end
850 end
851
851
852 should "be nil if there are no issues on the project" do
852 should "be nil if there are no issues on the project" do
853 assert_nil @project.start_date
853 assert_nil @project.start_date
854 end
854 end
855
855
856 should "be nil if issue tracking is disabled" do
856 should "be nil if issue tracking is disabled" do
857 Issue.generate_for_project!(@project, :start_date => Date.today)
857 Issue.generate_for_project!(@project, :start_date => Date.today)
858 @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
858 @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
859 @project.reload
859 @project.reload
860
860
861 assert_nil @project.start_date
861 assert_nil @project.start_date
862 end
862 end
863
863
864 should "be tested when issues have no start date"
864 should "be tested when issues have no start date"
865
865
866 should "be the earliest start date of it's issues" do
866 should "be the earliest start date of it's issues" do
867 early = 7.days.ago.to_date
867 early = 7.days.ago.to_date
868 Issue.generate_for_project!(@project, :start_date => Date.today)
868 Issue.generate_for_project!(@project, :start_date => Date.today)
869 Issue.generate_for_project!(@project, :start_date => early)
869 Issue.generate_for_project!(@project, :start_date => early)
870
870
871 assert_equal early, @project.start_date
871 assert_equal early, @project.start_date
872 end
872 end
873
873
874 end
874 end
875
875
876 context "#due_date" do
876 context "#due_date" do
877 setup do
877 setup do
878 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
878 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
879 @project = Project.generate!(:identifier => 'test0')
879 @project = Project.generate!(:identifier => 'test0')
880 @project.trackers << Tracker.generate!
880 @project.trackers << Tracker.generate!
881 end
881 end
882
882
883 should "be nil if there are no issues on the project" do
883 should "be nil if there are no issues on the project" do
884 assert_nil @project.due_date
884 assert_nil @project.due_date
885 end
885 end
886
886
887 should "be nil if issue tracking is disabled" do
887 should "be nil if issue tracking is disabled" do
888 Issue.generate_for_project!(@project, :due_date => Date.today)
888 Issue.generate_for_project!(@project, :due_date => Date.today)
889 @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
889 @project.enabled_modules.find_all_by_name('issue_tracking').each {|m| m.destroy}
890 @project.reload
890 @project.reload
891
891
892 assert_nil @project.due_date
892 assert_nil @project.due_date
893 end
893 end
894
894
895 should "be tested when issues have no due date"
895 should "be tested when issues have no due date"
896
896
897 should "be the latest due date of it's issues" do
897 should "be the latest due date of it's issues" do
898 future = 7.days.from_now.to_date
898 future = 7.days.from_now.to_date
899 Issue.generate_for_project!(@project, :due_date => future)
899 Issue.generate_for_project!(@project, :due_date => future)
900 Issue.generate_for_project!(@project, :due_date => Date.today)
900 Issue.generate_for_project!(@project, :due_date => Date.today)
901
901
902 assert_equal future, @project.due_date
902 assert_equal future, @project.due_date
903 end
903 end
904
904
905 should "be the latest due date of it's versions" do
905 should "be the latest due date of it's versions" do
906 future = 7.days.from_now.to_date
906 future = 7.days.from_now.to_date
907 @project.versions << Version.generate!(:effective_date => future)
907 @project.versions << Version.generate!(:effective_date => future)
908 @project.versions << Version.generate!(:effective_date => Date.today)
908 @project.versions << Version.generate!(:effective_date => Date.today)
909
909
910
910
911 assert_equal future, @project.due_date
911 assert_equal future, @project.due_date
912
912
913 end
913 end
914
914
915 should "pick the latest date from it's issues and versions" do
915 should "pick the latest date from it's issues and versions" do
916 future = 7.days.from_now.to_date
916 future = 7.days.from_now.to_date
917 far_future = 14.days.from_now.to_date
917 far_future = 14.days.from_now.to_date
918 Issue.generate_for_project!(@project, :due_date => far_future)
918 Issue.generate_for_project!(@project, :due_date => far_future)
919 @project.versions << Version.generate!(:effective_date => future)
919 @project.versions << Version.generate!(:effective_date => future)
920
920
921 assert_equal far_future, @project.due_date
921 assert_equal far_future, @project.due_date
922 end
922 end
923
923
924 end
924 end
925
925
926 context "Project#completed_percent" do
926 context "Project#completed_percent" do
927 setup do
927 setup do
928 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
928 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
929 @project = Project.generate!(:identifier => 'test0')
929 @project = Project.generate!(:identifier => 'test0')
930 @project.trackers << Tracker.generate!
930 @project.trackers << Tracker.generate!
931 end
931 end
932
932
933 context "no versions" do
933 context "no versions" do
934 should "be 100" do
934 should "be 100" do
935 assert_equal 100, @project.completed_percent
935 assert_equal 100, @project.completed_percent
936 end
936 end
937 end
937 end
938
938
939 context "with versions" do
939 context "with versions" do
940 should "return 0 if the versions have no issues" do
940 should "return 0 if the versions have no issues" do
941 Version.generate!(:project => @project)
941 Version.generate!(:project => @project)
942 Version.generate!(:project => @project)
942 Version.generate!(:project => @project)
943
943
944 assert_equal 0, @project.completed_percent
944 assert_equal 0, @project.completed_percent
945 end
945 end
946
946
947 should "return 100 if the version has only closed issues" do
947 should "return 100 if the version has only closed issues" do
948 v1 = Version.generate!(:project => @project)
948 v1 = Version.generate!(:project => @project)
949 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
949 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
950 v2 = Version.generate!(:project => @project)
950 v2 = Version.generate!(:project => @project)
951 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
951 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
952
952
953 assert_equal 100, @project.completed_percent
953 assert_equal 100, @project.completed_percent
954 end
954 end
955
955
956 should "return the averaged completed percent of the versions (not weighted)" do
956 should "return the averaged completed percent of the versions (not weighted)" do
957 v1 = Version.generate!(:project => @project)
957 v1 = Version.generate!(:project => @project)
958 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
958 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
959 v2 = Version.generate!(:project => @project)
959 v2 = Version.generate!(:project => @project)
960 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
960 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
961
961
962 assert_equal 50, @project.completed_percent
962 assert_equal 50, @project.completed_percent
963 end
963 end
964
964
965 end
965 end
966 end
966 end
967
968 context "#notified_users" do
969 setup do
970 @project = Project.generate!
971 @role = Role.generate!
972
973 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
974 Member.generate!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
975
976 @all_events_user = User.generate!(:mail_notification => 'all')
977 Member.generate!(:project => @project, :roles => [@role], :principal => @all_events_user)
978
979 @no_events_user = User.generate!(:mail_notification => 'none')
980 Member.generate!(:project => @project, :roles => [@role], :principal => @no_events_user)
981
982 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
983 Member.generate!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
984
985 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
986 Member.generate!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
987
988 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
989 Member.generate!(:project => @project, :roles => [@role], :principal => @only_owned_user)
990 end
991
992 should "include members with a mail notification" do
993 assert @project.notified_users.include?(@user_with_membership_notification)
994 end
995
996 should "include users with the 'all' notification option" do
997 assert @project.notified_users.include?(@all_events_user)
998 end
999
1000 should "not include users with the 'none' notification option" do
1001 assert !@project.notified_users.include?(@no_events_user)
1002 end
1003
1004 should "not include users with the 'only_my_events' notification option" do
1005 assert !@project.notified_users.include?(@only_my_events_user)
1006 end
1007
1008 should "not include users with the 'only_assigned' notification option" do
1009 assert !@project.notified_users.include?(@only_assigned_user)
1010 end
1011
1012 should "not include users with the 'only_owner' notification option" do
1013 assert !@project.notified_users.include?(@only_owned_user)
1014 end
1015 end
1016
967 end
1017 end
General Comments 0
You need to be logged in to leave comments. Login now