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