##// END OF EJS Templates
Fixed that target version is lost on project copy for issues that are assigned to a shared version from another project....
Jean-Philippe Lang -
r10150:5e9320137b09
parent child
Show More
@@ -1,954 +1,954
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
766 # Note: issues assigned to a closed version won't be copied due to validation rules
767 def copy_issues(project)
767 def copy_issues(project)
768 # Stores the source issue id as a key and the copied issues as the
768 # 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.
769 # value. Used to map the two togeather for issue relations.
770 issues_map = {}
770 issues_map = {}
771
771
772 # Get issues sorted by root_id, lft so that parent issues
772 # Get issues sorted by root_id, lft so that parent issues
773 # get copied before their children
773 # get copied before their children
774 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
774 project.issues.find(:all, :order => 'root_id, lft').each do |issue|
775 new_issue = Issue.new
775 new_issue = Issue.new
776 new_issue.copy_from(issue, :subtasks => false)
776 new_issue.copy_from(issue, :subtasks => false)
777 new_issue.project = self
777 new_issue.project = self
778 # Reassign fixed_versions by name, since names are unique per project
778 # Reassign fixed_versions by name, since names are unique per project
779 if issue.fixed_version
779 if issue.fixed_version && issue.fixed_version.project == project
780 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
780 new_issue.fixed_version = self.versions.detect {|v| v.name == issue.fixed_version.name}
781 end
781 end
782 # Reassign the category by name, since names are unique per project
782 # Reassign the category by name, since names are unique per project
783 if issue.category
783 if issue.category
784 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
784 new_issue.category = self.issue_categories.detect {|c| c.name == issue.category.name}
785 end
785 end
786 # Parent issue
786 # Parent issue
787 if issue.parent_id
787 if issue.parent_id
788 if copied_parent = issues_map[issue.parent_id]
788 if copied_parent = issues_map[issue.parent_id]
789 new_issue.parent_issue_id = copied_parent.id
789 new_issue.parent_issue_id = copied_parent.id
790 end
790 end
791 end
791 end
792
792
793 self.issues << new_issue
793 self.issues << new_issue
794 if new_issue.new_record?
794 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
795 logger.info "Project#copy_issues: issue ##{issue.id} could not be copied: #{new_issue.errors.full_messages}" if logger && logger.info
796 else
796 else
797 issues_map[issue.id] = new_issue unless new_issue.new_record?
797 issues_map[issue.id] = new_issue unless new_issue.new_record?
798 end
798 end
799 end
799 end
800
800
801 # Relations after in case issues related each other
801 # Relations after in case issues related each other
802 project.issues.each do |issue|
802 project.issues.each do |issue|
803 new_issue = issues_map[issue.id]
803 new_issue = issues_map[issue.id]
804 unless new_issue
804 unless new_issue
805 # Issue was not copied
805 # Issue was not copied
806 next
806 next
807 end
807 end
808
808
809 # Relations
809 # Relations
810 issue.relations_from.each do |source_relation|
810 issue.relations_from.each do |source_relation|
811 new_issue_relation = IssueRelation.new
811 new_issue_relation = IssueRelation.new
812 new_issue_relation.attributes = source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
812 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]
813 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?
814 if new_issue_relation.issue_to.nil? && Setting.cross_project_issue_relations?
815 new_issue_relation.issue_to = source_relation.issue_to
815 new_issue_relation.issue_to = source_relation.issue_to
816 end
816 end
817 new_issue.relations_from << new_issue_relation
817 new_issue.relations_from << new_issue_relation
818 end
818 end
819
819
820 issue.relations_to.each do |source_relation|
820 issue.relations_to.each do |source_relation|
821 new_issue_relation = IssueRelation.new
821 new_issue_relation = IssueRelation.new
822 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")
823 new_issue_relation.issue_from = issues_map[source_relation.issue_from_id]
823 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?
824 if new_issue_relation.issue_from.nil? && Setting.cross_project_issue_relations?
825 new_issue_relation.issue_from = source_relation.issue_from
825 new_issue_relation.issue_from = source_relation.issue_from
826 end
826 end
827 new_issue.relations_to << new_issue_relation
827 new_issue.relations_to << new_issue_relation
828 end
828 end
829 end
829 end
830 end
830 end
831
831
832 # Copies members from +project+
832 # Copies members from +project+
833 def copy_members(project)
833 def copy_members(project)
834 # Copy users first, then groups to handle members with inherited and given roles
834 # Copy users first, then groups to handle members with inherited and given roles
835 members_to_copy = []
835 members_to_copy = []
836 members_to_copy += project.memberships.select {|m| m.principal.is_a?(User)}
836 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)}
837 members_to_copy += project.memberships.select {|m| !m.principal.is_a?(User)}
838
838
839 members_to_copy.each do |member|
839 members_to_copy.each do |member|
840 new_member = Member.new
840 new_member = Member.new
841 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
841 new_member.attributes = member.attributes.dup.except("id", "project_id", "created_on")
842 # only copy non inherited roles
842 # only copy non inherited roles
843 # inherited roles will be added when copying the group membership
843 # inherited roles will be added when copying the group membership
844 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
844 role_ids = member.member_roles.reject(&:inherited?).collect(&:role_id)
845 next if role_ids.empty?
845 next if role_ids.empty?
846 new_member.role_ids = role_ids
846 new_member.role_ids = role_ids
847 new_member.project = self
847 new_member.project = self
848 self.members << new_member
848 self.members << new_member
849 end
849 end
850 end
850 end
851
851
852 # Copies queries from +project+
852 # Copies queries from +project+
853 def copy_queries(project)
853 def copy_queries(project)
854 project.queries.each do |query|
854 project.queries.each do |query|
855 new_query = ::Query.new
855 new_query = ::Query.new
856 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
856 new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria")
857 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
857 new_query.sort_criteria = query.sort_criteria if query.sort_criteria
858 new_query.project = self
858 new_query.project = self
859 new_query.user_id = query.user_id
859 new_query.user_id = query.user_id
860 self.queries << new_query
860 self.queries << new_query
861 end
861 end
862 end
862 end
863
863
864 # Copies boards from +project+
864 # Copies boards from +project+
865 def copy_boards(project)
865 def copy_boards(project)
866 project.boards.each do |board|
866 project.boards.each do |board|
867 new_board = Board.new
867 new_board = Board.new
868 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
868 new_board.attributes = board.attributes.dup.except("id", "project_id", "topics_count", "messages_count", "last_message_id")
869 new_board.project = self
869 new_board.project = self
870 self.boards << new_board
870 self.boards << new_board
871 end
871 end
872 end
872 end
873
873
874 def allowed_permissions
874 def allowed_permissions
875 @allowed_permissions ||= begin
875 @allowed_permissions ||= begin
876 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
876 module_names = enabled_modules.all(:select => :name).collect {|m| m.name}
877 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
877 Redmine::AccessControl.modules_permissions(module_names).collect {|p| p.name}
878 end
878 end
879 end
879 end
880
880
881 def allowed_actions
881 def allowed_actions
882 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
882 @actions_allowed ||= allowed_permissions.inject([]) { |actions, permission| actions += Redmine::AccessControl.allowed_actions(permission) }.flatten
883 end
883 end
884
884
885 # Returns all the active Systemwide and project specific activities
885 # Returns all the active Systemwide and project specific activities
886 def active_activities
886 def active_activities
887 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
887 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
888
888
889 if overridden_activity_ids.empty?
889 if overridden_activity_ids.empty?
890 return TimeEntryActivity.shared.active
890 return TimeEntryActivity.shared.active
891 else
891 else
892 return system_activities_and_project_overrides
892 return system_activities_and_project_overrides
893 end
893 end
894 end
894 end
895
895
896 # Returns all the Systemwide and project specific activities
896 # Returns all the Systemwide and project specific activities
897 # (inactive and active)
897 # (inactive and active)
898 def all_activities
898 def all_activities
899 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
899 overridden_activity_ids = self.time_entry_activities.collect(&:parent_id)
900
900
901 if overridden_activity_ids.empty?
901 if overridden_activity_ids.empty?
902 return TimeEntryActivity.shared
902 return TimeEntryActivity.shared
903 else
903 else
904 return system_activities_and_project_overrides(true)
904 return system_activities_and_project_overrides(true)
905 end
905 end
906 end
906 end
907
907
908 # Returns the systemwide active activities merged with the project specific overrides
908 # Returns the systemwide active activities merged with the project specific overrides
909 def system_activities_and_project_overrides(include_inactive=false)
909 def system_activities_and_project_overrides(include_inactive=false)
910 if include_inactive
910 if include_inactive
911 return TimeEntryActivity.shared.
911 return TimeEntryActivity.shared.
912 find(:all,
912 find(:all,
913 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
913 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
914 self.time_entry_activities
914 self.time_entry_activities
915 else
915 else
916 return TimeEntryActivity.shared.active.
916 return TimeEntryActivity.shared.active.
917 find(:all,
917 find(:all,
918 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
918 :conditions => ["id NOT IN (?)", self.time_entry_activities.collect(&:parent_id)]) +
919 self.time_entry_activities.active
919 self.time_entry_activities.active
920 end
920 end
921 end
921 end
922
922
923 # Archives subprojects recursively
923 # Archives subprojects recursively
924 def archive!
924 def archive!
925 children.each do |subproject|
925 children.each do |subproject|
926 subproject.send :archive!
926 subproject.send :archive!
927 end
927 end
928 update_attribute :status, STATUS_ARCHIVED
928 update_attribute :status, STATUS_ARCHIVED
929 end
929 end
930
930
931 def update_position_under_parent
931 def update_position_under_parent
932 set_or_update_position_under(parent)
932 set_or_update_position_under(parent)
933 end
933 end
934
934
935 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted
935 # Inserts/moves the project so that target's children or root projects stay alphabetically sorted
936 def set_or_update_position_under(target_parent)
936 def set_or_update_position_under(target_parent)
937 sibs = (target_parent.nil? ? self.class.roots : target_parent.children)
937 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 }
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 }
939
939
940 if to_be_inserted_before
940 if to_be_inserted_before
941 move_to_left_of(to_be_inserted_before)
941 move_to_left_of(to_be_inserted_before)
942 elsif target_parent.nil?
942 elsif target_parent.nil?
943 if sibs.empty?
943 if sibs.empty?
944 # move_to_root adds the project in first (ie. left) position
944 # move_to_root adds the project in first (ie. left) position
945 move_to_root
945 move_to_root
946 else
946 else
947 move_to_right_of(sibs.last) unless self == sibs.last
947 move_to_right_of(sibs.last) unless self == sibs.last
948 end
948 end
949 else
949 else
950 # move_to_child_of adds the project in last (ie.right) position
950 # move_to_child_of adds the project in last (ie.right) position
951 move_to_child_of(target_parent)
951 move_to_child_of(target_parent)
952 end
952 end
953 end
953 end
954 end
954 end
@@ -1,1196 +1,1212
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 "change the new issues to use the copied version" do
826 should "change the new issues to use the copied version" do
827 User.current = User.find(1)
827 User.current = User.find(1)
828 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
828 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open')
829 @source_project.versions << assigned_version
829 @source_project.versions << assigned_version
830 assert_equal 3, @source_project.versions.size
830 assert_equal 3, @source_project.versions.size
831 Issue.generate_for_project!(@source_project,
831 Issue.generate_for_project!(@source_project,
832 :fixed_version_id => assigned_version.id,
832 :fixed_version_id => assigned_version.id,
833 :subject => "change the new issues to use the copied version",
833 :subject => "change the new issues to use the copied version",
834 :tracker_id => 1,
834 :tracker_id => 1,
835 :project_id => @source_project.id)
835 :project_id => @source_project.id)
836
836
837 assert @project.copy(@source_project)
837 assert @project.copy(@source_project)
838 @project.reload
838 @project.reload
839 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
839 copied_issue = @project.issues.first(:conditions => {:subject => "change the new issues to use the copied version"})
840
840
841 assert copied_issue
841 assert copied_issue
842 assert copied_issue.fixed_version
842 assert copied_issue.fixed_version
843 assert_equal "Assigned Issues", copied_issue.fixed_version.name # Same name
843 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
844 assert_not_equal assigned_version.id, copied_issue.fixed_version.id # Different record
845 end
845 end
846
846
847 should "keep target shared versions from other project" do
848 assigned_version = Version.generate!(:name => "Assigned Issues", :status => 'open', :project_id => 1, :sharing => 'system')
849 issue = Issue.generate_for_project!(@source_project,
850 :fixed_version => assigned_version,
851 :subject => "keep target shared versions",
852 :tracker_id => 1,
853 :project_id => @source_project.id)
854
855 assert @project.copy(@source_project)
856 @project.reload
857 copied_issue = @project.issues.first(:conditions => {:subject => "keep target shared versions"})
858
859 assert copied_issue
860 assert_equal assigned_version, copied_issue.fixed_version
861 end
862
847 should "copy issue relations" do
863 should "copy issue relations" do
848 Setting.cross_project_issue_relations = '1'
864 Setting.cross_project_issue_relations = '1'
849
865
850 second_issue = Issue.generate!(:status_id => 5,
866 second_issue = Issue.generate!(:status_id => 5,
851 :subject => "copy issue relation",
867 :subject => "copy issue relation",
852 :tracker_id => 1,
868 :tracker_id => 1,
853 :assigned_to_id => 2,
869 :assigned_to_id => 2,
854 :project_id => @source_project.id)
870 :project_id => @source_project.id)
855 source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
871 source_relation = IssueRelation.create!(:issue_from => Issue.find(4),
856 :issue_to => second_issue,
872 :issue_to => second_issue,
857 :relation_type => "relates")
873 :relation_type => "relates")
858 source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
874 source_relation_cross_project = IssueRelation.create!(:issue_from => Issue.find(1),
859 :issue_to => second_issue,
875 :issue_to => second_issue,
860 :relation_type => "duplicates")
876 :relation_type => "duplicates")
861
877
862 assert @project.copy(@source_project)
878 assert @project.copy(@source_project)
863 assert_equal @source_project.issues.count, @project.issues.count
879 assert_equal @source_project.issues.count, @project.issues.count
864 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
880 copied_issue = @project.issues.find_by_subject("Issue on project 2") # Was #4
865 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
881 copied_second_issue = @project.issues.find_by_subject("copy issue relation")
866
882
867 # First issue with a relation on project
883 # First issue with a relation on project
868 assert_equal 1, copied_issue.relations.size, "Relation not copied"
884 assert_equal 1, copied_issue.relations.size, "Relation not copied"
869 copied_relation = copied_issue.relations.first
885 copied_relation = copied_issue.relations.first
870 assert_equal "relates", copied_relation.relation_type
886 assert_equal "relates", copied_relation.relation_type
871 assert_equal copied_second_issue.id, copied_relation.issue_to_id
887 assert_equal copied_second_issue.id, copied_relation.issue_to_id
872 assert_not_equal source_relation.id, copied_relation.id
888 assert_not_equal source_relation.id, copied_relation.id
873
889
874 # Second issue with a cross project relation
890 # Second issue with a cross project relation
875 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
891 assert_equal 2, copied_second_issue.relations.size, "Relation not copied"
876 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
892 copied_relation = copied_second_issue.relations.select {|r| r.relation_type == 'duplicates'}.first
877 assert_equal "duplicates", copied_relation.relation_type
893 assert_equal "duplicates", copied_relation.relation_type
878 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
894 assert_equal 1, copied_relation.issue_from_id, "Cross project relation not kept"
879 assert_not_equal source_relation_cross_project.id, copied_relation.id
895 assert_not_equal source_relation_cross_project.id, copied_relation.id
880 end
896 end
881
897
882 should "copy issue attachments" do
898 should "copy issue attachments" do
883 issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
899 issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
884 Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
900 Attachment.create!(:container => issue, :file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 1)
885 @source_project.issues << issue
901 @source_project.issues << issue
886 assert @project.copy(@source_project)
902 assert @project.copy(@source_project)
887
903
888 copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
904 copied_issue = @project.issues.first(:conditions => {:subject => "copy with attachment"})
889 assert_not_nil copied_issue
905 assert_not_nil copied_issue
890 assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
906 assert_equal 1, copied_issue.attachments.count, "Attachment not copied"
891 assert_equal "testfile.txt", copied_issue.attachments.first.filename
907 assert_equal "testfile.txt", copied_issue.attachments.first.filename
892 end
908 end
893
909
894 should "copy memberships" do
910 should "copy memberships" do
895 assert @project.valid?
911 assert @project.valid?
896 assert @project.members.empty?
912 assert @project.members.empty?
897 assert @project.copy(@source_project)
913 assert @project.copy(@source_project)
898
914
899 assert_equal @source_project.memberships.size, @project.memberships.size
915 assert_equal @source_project.memberships.size, @project.memberships.size
900 @project.memberships.each do |membership|
916 @project.memberships.each do |membership|
901 assert membership
917 assert membership
902 assert_equal @project, membership.project
918 assert_equal @project, membership.project
903 end
919 end
904 end
920 end
905
921
906 should "copy memberships with groups and additional roles" do
922 should "copy memberships with groups and additional roles" do
907 group = Group.create!(:lastname => "Copy group")
923 group = Group.create!(:lastname => "Copy group")
908 user = User.find(7)
924 user = User.find(7)
909 group.users << user
925 group.users << user
910 # group role
926 # group role
911 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
927 Member.create!(:project_id => @source_project.id, :principal => group, :role_ids => [2])
912 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
928 member = Member.find_by_user_id_and_project_id(user.id, @source_project.id)
913 # additional role
929 # additional role
914 member.role_ids = [1]
930 member.role_ids = [1]
915
931
916 assert @project.copy(@source_project)
932 assert @project.copy(@source_project)
917 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
933 member = Member.find_by_user_id_and_project_id(user.id, @project.id)
918 assert_not_nil member
934 assert_not_nil member
919 assert_equal [1, 2], member.role_ids.sort
935 assert_equal [1, 2], member.role_ids.sort
920 end
936 end
921
937
922 should "copy project specific queries" do
938 should "copy project specific queries" do
923 assert @project.valid?
939 assert @project.valid?
924 assert @project.queries.empty?
940 assert @project.queries.empty?
925 assert @project.copy(@source_project)
941 assert @project.copy(@source_project)
926
942
927 assert_equal @source_project.queries.size, @project.queries.size
943 assert_equal @source_project.queries.size, @project.queries.size
928 @project.queries.each do |query|
944 @project.queries.each do |query|
929 assert query
945 assert query
930 assert_equal @project, query.project
946 assert_equal @project, query.project
931 end
947 end
932 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
948 assert_equal @source_project.queries.map(&:user_id).sort, @project.queries.map(&:user_id).sort
933 end
949 end
934
950
935 should "copy versions" do
951 should "copy versions" do
936 @source_project.versions << Version.generate!
952 @source_project.versions << Version.generate!
937 @source_project.versions << Version.generate!
953 @source_project.versions << Version.generate!
938
954
939 assert @project.versions.empty?
955 assert @project.versions.empty?
940 assert @project.copy(@source_project)
956 assert @project.copy(@source_project)
941
957
942 assert_equal @source_project.versions.size, @project.versions.size
958 assert_equal @source_project.versions.size, @project.versions.size
943 @project.versions.each do |version|
959 @project.versions.each do |version|
944 assert version
960 assert version
945 assert_equal @project, version.project
961 assert_equal @project, version.project
946 end
962 end
947 end
963 end
948
964
949 should "copy wiki" do
965 should "copy wiki" do
950 assert_difference 'Wiki.count' do
966 assert_difference 'Wiki.count' do
951 assert @project.copy(@source_project)
967 assert @project.copy(@source_project)
952 end
968 end
953
969
954 assert @project.wiki
970 assert @project.wiki
955 assert_not_equal @source_project.wiki, @project.wiki
971 assert_not_equal @source_project.wiki, @project.wiki
956 assert_equal "Start page", @project.wiki.start_page
972 assert_equal "Start page", @project.wiki.start_page
957 end
973 end
958
974
959 should "copy wiki pages and content with hierarchy" do
975 should "copy wiki pages and content with hierarchy" do
960 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
976 assert_difference 'WikiPage.count', @source_project.wiki.pages.size do
961 assert @project.copy(@source_project)
977 assert @project.copy(@source_project)
962 end
978 end
963
979
964 assert @project.wiki
980 assert @project.wiki
965 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
981 assert_equal @source_project.wiki.pages.size, @project.wiki.pages.size
966
982
967 @project.wiki.pages.each do |wiki_page|
983 @project.wiki.pages.each do |wiki_page|
968 assert wiki_page.content
984 assert wiki_page.content
969 assert !@source_project.wiki.pages.include?(wiki_page)
985 assert !@source_project.wiki.pages.include?(wiki_page)
970 end
986 end
971
987
972 parent = @project.wiki.find_page('Parent_page')
988 parent = @project.wiki.find_page('Parent_page')
973 child1 = @project.wiki.find_page('Child_page_1')
989 child1 = @project.wiki.find_page('Child_page_1')
974 child2 = @project.wiki.find_page('Child_page_2')
990 child2 = @project.wiki.find_page('Child_page_2')
975 assert_equal parent, child1.parent
991 assert_equal parent, child1.parent
976 assert_equal parent, child2.parent
992 assert_equal parent, child2.parent
977 end
993 end
978
994
979 should "copy issue categories" do
995 should "copy issue categories" do
980 assert @project.copy(@source_project)
996 assert @project.copy(@source_project)
981
997
982 assert_equal 2, @project.issue_categories.size
998 assert_equal 2, @project.issue_categories.size
983 @project.issue_categories.each do |issue_category|
999 @project.issue_categories.each do |issue_category|
984 assert !@source_project.issue_categories.include?(issue_category)
1000 assert !@source_project.issue_categories.include?(issue_category)
985 end
1001 end
986 end
1002 end
987
1003
988 should "copy boards" do
1004 should "copy boards" do
989 assert @project.copy(@source_project)
1005 assert @project.copy(@source_project)
990
1006
991 assert_equal 1, @project.boards.size
1007 assert_equal 1, @project.boards.size
992 @project.boards.each do |board|
1008 @project.boards.each do |board|
993 assert !@source_project.boards.include?(board)
1009 assert !@source_project.boards.include?(board)
994 end
1010 end
995 end
1011 end
996
1012
997 should "change the new issues to use the copied issue categories" do
1013 should "change the new issues to use the copied issue categories" do
998 issue = Issue.find(4)
1014 issue = Issue.find(4)
999 issue.update_attribute(:category_id, 3)
1015 issue.update_attribute(:category_id, 3)
1000
1016
1001 assert @project.copy(@source_project)
1017 assert @project.copy(@source_project)
1002
1018
1003 @project.issues.each do |issue|
1019 @project.issues.each do |issue|
1004 assert issue.category
1020 assert issue.category
1005 assert_equal "Stock management", issue.category.name # Same name
1021 assert_equal "Stock management", issue.category.name # Same name
1006 assert_not_equal IssueCategory.find(3), issue.category # Different record
1022 assert_not_equal IssueCategory.find(3), issue.category # Different record
1007 end
1023 end
1008 end
1024 end
1009
1025
1010 should "limit copy with :only option" do
1026 should "limit copy with :only option" do
1011 assert @project.members.empty?
1027 assert @project.members.empty?
1012 assert @project.issue_categories.empty?
1028 assert @project.issue_categories.empty?
1013 assert @source_project.issues.any?
1029 assert @source_project.issues.any?
1014
1030
1015 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
1031 assert @project.copy(@source_project, :only => ['members', 'issue_categories'])
1016
1032
1017 assert @project.members.any?
1033 assert @project.members.any?
1018 assert @project.issue_categories.any?
1034 assert @project.issue_categories.any?
1019 assert @project.issues.empty?
1035 assert @project.issues.empty?
1020 end
1036 end
1021 end
1037 end
1022
1038
1023 def test_copy_should_copy_subtasks
1039 def test_copy_should_copy_subtasks
1024 source = Project.generate!(:tracker_ids => [1])
1040 source = Project.generate!(:tracker_ids => [1])
1025 issue = Issue.generate_with_descendants!(source, :subject => 'Parent')
1041 issue = Issue.generate_with_descendants!(source, :subject => 'Parent')
1026 project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
1042 project = Project.new(:name => 'Copy', :identifier => 'copy', :tracker_ids => [1])
1027
1043
1028 assert_difference 'Project.count' do
1044 assert_difference 'Project.count' do
1029 assert_difference 'Issue.count', 1+issue.descendants.count do
1045 assert_difference 'Issue.count', 1+issue.descendants.count do
1030 assert project.copy(source.reload)
1046 assert project.copy(source.reload)
1031 end
1047 end
1032 end
1048 end
1033 copy = Issue.where(:parent_id => nil).order("id DESC").first
1049 copy = Issue.where(:parent_id => nil).order("id DESC").first
1034 assert_equal project, copy.project
1050 assert_equal project, copy.project
1035 assert_equal issue.descendants.count, copy.descendants.count
1051 assert_equal issue.descendants.count, copy.descendants.count
1036 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1052 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
1037 assert child_copy.descendants.any?
1053 assert child_copy.descendants.any?
1038 end
1054 end
1039
1055
1040 context "#start_date" do
1056 context "#start_date" do
1041 setup do
1057 setup do
1042 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1058 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1043 @project = Project.generate!(:identifier => 'test0')
1059 @project = Project.generate!(:identifier => 'test0')
1044 @project.trackers << Tracker.generate!
1060 @project.trackers << Tracker.generate!
1045 end
1061 end
1046
1062
1047 should "be nil if there are no issues on the project" do
1063 should "be nil if there are no issues on the project" do
1048 assert_nil @project.start_date
1064 assert_nil @project.start_date
1049 end
1065 end
1050
1066
1051 should "be tested when issues have no start date"
1067 should "be tested when issues have no start date"
1052
1068
1053 should "be the earliest start date of it's issues" do
1069 should "be the earliest start date of it's issues" do
1054 early = 7.days.ago.to_date
1070 early = 7.days.ago.to_date
1055 Issue.generate_for_project!(@project, :start_date => Date.today)
1071 Issue.generate_for_project!(@project, :start_date => Date.today)
1056 Issue.generate_for_project!(@project, :start_date => early)
1072 Issue.generate_for_project!(@project, :start_date => early)
1057
1073
1058 assert_equal early, @project.start_date
1074 assert_equal early, @project.start_date
1059 end
1075 end
1060
1076
1061 end
1077 end
1062
1078
1063 context "#due_date" do
1079 context "#due_date" do
1064 setup do
1080 setup do
1065 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1081 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1066 @project = Project.generate!(:identifier => 'test0')
1082 @project = Project.generate!(:identifier => 'test0')
1067 @project.trackers << Tracker.generate!
1083 @project.trackers << Tracker.generate!
1068 end
1084 end
1069
1085
1070 should "be nil if there are no issues on the project" do
1086 should "be nil if there are no issues on the project" do
1071 assert_nil @project.due_date
1087 assert_nil @project.due_date
1072 end
1088 end
1073
1089
1074 should "be tested when issues have no due date"
1090 should "be tested when issues have no due date"
1075
1091
1076 should "be the latest due date of it's issues" do
1092 should "be the latest due date of it's issues" do
1077 future = 7.days.from_now.to_date
1093 future = 7.days.from_now.to_date
1078 Issue.generate_for_project!(@project, :due_date => future)
1094 Issue.generate_for_project!(@project, :due_date => future)
1079 Issue.generate_for_project!(@project, :due_date => Date.today)
1095 Issue.generate_for_project!(@project, :due_date => Date.today)
1080
1096
1081 assert_equal future, @project.due_date
1097 assert_equal future, @project.due_date
1082 end
1098 end
1083
1099
1084 should "be the latest due date of it's versions" do
1100 should "be the latest due date of it's versions" do
1085 future = 7.days.from_now.to_date
1101 future = 7.days.from_now.to_date
1086 @project.versions << Version.generate!(:effective_date => future)
1102 @project.versions << Version.generate!(:effective_date => future)
1087 @project.versions << Version.generate!(:effective_date => Date.today)
1103 @project.versions << Version.generate!(:effective_date => Date.today)
1088
1104
1089
1105
1090 assert_equal future, @project.due_date
1106 assert_equal future, @project.due_date
1091
1107
1092 end
1108 end
1093
1109
1094 should "pick the latest date from it's issues and versions" do
1110 should "pick the latest date from it's issues and versions" do
1095 future = 7.days.from_now.to_date
1111 future = 7.days.from_now.to_date
1096 far_future = 14.days.from_now.to_date
1112 far_future = 14.days.from_now.to_date
1097 Issue.generate_for_project!(@project, :due_date => far_future)
1113 Issue.generate_for_project!(@project, :due_date => far_future)
1098 @project.versions << Version.generate!(:effective_date => future)
1114 @project.versions << Version.generate!(:effective_date => future)
1099
1115
1100 assert_equal far_future, @project.due_date
1116 assert_equal far_future, @project.due_date
1101 end
1117 end
1102
1118
1103 end
1119 end
1104
1120
1105 context "Project#completed_percent" do
1121 context "Project#completed_percent" do
1106 setup do
1122 setup do
1107 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1123 ProjectCustomField.destroy_all # Custom values are a mess to isolate in tests
1108 @project = Project.generate!(:identifier => 'test0')
1124 @project = Project.generate!(:identifier => 'test0')
1109 @project.trackers << Tracker.generate!
1125 @project.trackers << Tracker.generate!
1110 end
1126 end
1111
1127
1112 context "no versions" do
1128 context "no versions" do
1113 should "be 100" do
1129 should "be 100" do
1114 assert_equal 100, @project.completed_percent
1130 assert_equal 100, @project.completed_percent
1115 end
1131 end
1116 end
1132 end
1117
1133
1118 context "with versions" do
1134 context "with versions" do
1119 should "return 0 if the versions have no issues" do
1135 should "return 0 if the versions have no issues" do
1120 Version.generate!(:project => @project)
1136 Version.generate!(:project => @project)
1121 Version.generate!(:project => @project)
1137 Version.generate!(:project => @project)
1122
1138
1123 assert_equal 0, @project.completed_percent
1139 assert_equal 0, @project.completed_percent
1124 end
1140 end
1125
1141
1126 should "return 100 if the version has only closed issues" do
1142 should "return 100 if the version has only closed issues" do
1127 v1 = Version.generate!(:project => @project)
1143 v1 = Version.generate!(:project => @project)
1128 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1144 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v1)
1129 v2 = Version.generate!(:project => @project)
1145 v2 = Version.generate!(:project => @project)
1130 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1146 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('Closed'), :fixed_version => v2)
1131
1147
1132 assert_equal 100, @project.completed_percent
1148 assert_equal 100, @project.completed_percent
1133 end
1149 end
1134
1150
1135 should "return the averaged completed percent of the versions (not weighted)" do
1151 should "return the averaged completed percent of the versions (not weighted)" do
1136 v1 = Version.generate!(:project => @project)
1152 v1 = Version.generate!(:project => @project)
1137 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1153 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v1)
1138 v2 = Version.generate!(:project => @project)
1154 v2 = Version.generate!(:project => @project)
1139 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1155 Issue.generate_for_project!(@project, :status => IssueStatus.find_by_name('New'), :estimated_hours => 10, :done_ratio => 50, :fixed_version => v2)
1140
1156
1141 assert_equal 50, @project.completed_percent
1157 assert_equal 50, @project.completed_percent
1142 end
1158 end
1143
1159
1144 end
1160 end
1145 end
1161 end
1146
1162
1147 context "#notified_users" do
1163 context "#notified_users" do
1148 setup do
1164 setup do
1149 @project = Project.generate!
1165 @project = Project.generate!
1150 @role = Role.generate!
1166 @role = Role.generate!
1151
1167
1152 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1168 @user_with_membership_notification = User.generate!(:mail_notification => 'selected')
1153 Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1169 Member.create!(:project => @project, :roles => [@role], :principal => @user_with_membership_notification, :mail_notification => true)
1154
1170
1155 @all_events_user = User.generate!(:mail_notification => 'all')
1171 @all_events_user = User.generate!(:mail_notification => 'all')
1156 Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user)
1172 Member.create!(:project => @project, :roles => [@role], :principal => @all_events_user)
1157
1173
1158 @no_events_user = User.generate!(:mail_notification => 'none')
1174 @no_events_user = User.generate!(:mail_notification => 'none')
1159 Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user)
1175 Member.create!(:project => @project, :roles => [@role], :principal => @no_events_user)
1160
1176
1161 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1177 @only_my_events_user = User.generate!(:mail_notification => 'only_my_events')
1162 Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1178 Member.create!(:project => @project, :roles => [@role], :principal => @only_my_events_user)
1163
1179
1164 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1180 @only_assigned_user = User.generate!(:mail_notification => 'only_assigned')
1165 Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1181 Member.create!(:project => @project, :roles => [@role], :principal => @only_assigned_user)
1166
1182
1167 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1183 @only_owned_user = User.generate!(:mail_notification => 'only_owner')
1168 Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1184 Member.create!(:project => @project, :roles => [@role], :principal => @only_owned_user)
1169 end
1185 end
1170
1186
1171 should "include members with a mail notification" do
1187 should "include members with a mail notification" do
1172 assert @project.notified_users.include?(@user_with_membership_notification)
1188 assert @project.notified_users.include?(@user_with_membership_notification)
1173 end
1189 end
1174
1190
1175 should "include users with the 'all' notification option" do
1191 should "include users with the 'all' notification option" do
1176 assert @project.notified_users.include?(@all_events_user)
1192 assert @project.notified_users.include?(@all_events_user)
1177 end
1193 end
1178
1194
1179 should "not include users with the 'none' notification option" do
1195 should "not include users with the 'none' notification option" do
1180 assert !@project.notified_users.include?(@no_events_user)
1196 assert !@project.notified_users.include?(@no_events_user)
1181 end
1197 end
1182
1198
1183 should "not include users with the 'only_my_events' notification option" do
1199 should "not include users with the 'only_my_events' notification option" do
1184 assert !@project.notified_users.include?(@only_my_events_user)
1200 assert !@project.notified_users.include?(@only_my_events_user)
1185 end
1201 end
1186
1202
1187 should "not include users with the 'only_assigned' notification option" do
1203 should "not include users with the 'only_assigned' notification option" do
1188 assert !@project.notified_users.include?(@only_assigned_user)
1204 assert !@project.notified_users.include?(@only_assigned_user)
1189 end
1205 end
1190
1206
1191 should "not include users with the 'only_owner' notification option" do
1207 should "not include users with the 'only_owner' notification option" do
1192 assert !@project.notified_users.include?(@only_owned_user)
1208 assert !@project.notified_users.include?(@only_owned_user)
1193 end
1209 end
1194 end
1210 end
1195
1211
1196 end
1212 end
General Comments 0
You need to be logged in to leave comments. Login now