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