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