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