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