Merge branch 'feature/rails3' into feature/copy_move_work_packages_between_projects

Conflicts:
	app/controllers/work_packages_controller.rb
	app/models/work_package.rb
pull/294/head
Hagen Schink 11 years ago
commit a357e2b6ba
  1. 4
      Gemfile
  2. 99
      Gemfile.lock
  3. 4
      app/controllers/activities_controller.rb
  4. 3
      app/controllers/admin_controller.rb
  5. 16
      app/controllers/members_controller.rb
  6. 137
      app/controllers/work_packages_controller.rb
  7. 13
      app/models/group.rb
  8. 109
      app/models/member.rb
  9. 33
      app/models/member_role.rb
  10. 48
      app/models/permitted_params.rb
  11. 6
      app/models/project.rb
  12. 4
      app/models/user.rb
  13. 26
      app/models/work_package.rb
  14. 8
      app/views/timelines/_form.html.erb
  15. 2
      app/views/work_packages/_edit.html.erb
  16. 3
      doc/CHANGELOG.md
  17. 11
      features/activities/index.feature
  18. 3
      features/projects/settings.feature
  19. 2
      features/work_packages/editable_fields.feature
  20. 5
      features/work_packages/error_on_update.feature
  21. 1
      features/work_packages/navigate_to_edit.feature
  22. 75
      features/work_packages/switch_type.feature
  23. 6
      lib/tasks/cucumber.rake
  24. 639
      spec/controllers/work_packages_controller_spec.rb
  25. 139
      spec/models/member_spec.rb
  26. 49
      spec/models/permitted_params_spec.rb
  27. 26
      spec/models/project_spec.rb
  28. 43
      spec/models/user_spec.rb
  29. 18
      spec/models/work_package_spec.rb
  30. 21
      spec/permissions/add_work_packages_spec.rb
  31. 21
      spec/permissions/edit_work_packages_spec.rb
  32. 19
      spec/permissions/view_work_packages_spec.rb
  33. 50
      spec/support/permission_specs.rb
  34. 29
      spec/support/shared/become_member.rb

@ -1,6 +1,6 @@
source 'https://rubygems.org'
gem "rails", :git => "https://github.com/rails/rails.git", :branch => "3-2-stable"
gem "rails", "~> 3.2.14"
gem "coderay", "~> 1.0.5"
gem "rubytree", "~> 0.8.3"
@ -52,7 +52,7 @@ end
gem "prototype-rails"
# remove once we no longer use the deprecated "link_to_remote", "remote_form_for" and alike methods
# replace those with :remote => true
gem 'prototype_legacy_helper', '0.0.0', :git => 'git://github.com/rails/prototype_legacy_helper.git'
gem 'prototype_legacy_helper', '0.0.0', :git => 'https://github.com/rails/prototype_legacy_helper.git'
gem 'jquery-rails', '~> 2.0.3'
# branch rewrite has commit 6bfdcd7e14df1efffc00b2bbdf4e14e614d00418 which adds

@ -1,9 +1,3 @@
GIT
remote: git://github.com/rails/prototype_legacy_helper.git
revision: a2cd95c3e3c1a4f7a9566efdab5ce59c886cb05f
specs:
prototype_legacy_helper (0.0.0)
GIT
remote: https://github.com/awebneck/object_daddy.git
revision: cf5abf001cdbd14b47a3b51421446717a3183f0a
@ -25,52 +19,10 @@ GIT
i18n
GIT
remote: https://github.com/rails/rails.git
revision: b0f96d4436619b25b8024cc70a71c77bcfc12bf6
branch: 3-2-stable
remote: https://github.com/rails/prototype_legacy_helper.git
revision: a2cd95c3e3c1a4f7a9566efdab5ce59c886cb05f
specs:
actionmailer (3.2.13)
actionpack (= 3.2.13)
mail (~> 2.5.4)
actionpack (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.13)
activesupport (= 3.2.13)
builder (~> 3.0.0)
activerecord (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.13)
activemodel (= 3.2.13)
activesupport (= 3.2.13)
activesupport (3.2.13)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
rails (3.2.13)
actionmailer (= 3.2.13)
actionpack (= 3.2.13)
activerecord (= 3.2.13)
activeresource (= 3.2.13)
activesupport (= 3.2.13)
bundler (~> 1.0)
railties (= 3.2.13)
railties (3.2.13)
actionpack (= 3.2.13)
activesupport (= 3.2.13)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
prototype_legacy_helper (0.0.0)
GIT
remote: https://github.com/svenfuchs/globalize3.git
@ -82,8 +34,36 @@ GIT
paper_trail (~> 2)
GEM
remote: https://rubygems.org/
remote: https://rubygems.org/
specs:
actionmailer (3.2.14)
actionpack (= 3.2.14)
mail (~> 2.5.4)
actionpack (3.2.14)
activemodel (= 3.2.14)
activesupport (= 3.2.14)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.14)
activesupport (= 3.2.14)
builder (~> 3.0.0)
activerecord (3.2.14)
activemodel (= 3.2.14)
activesupport (= 3.2.14)
arel (~> 3.0.2)
tzinfo (~> 0.3.29)
activeresource (3.2.14)
activemodel (= 3.2.14)
activesupport (= 3.2.14)
activesupport (3.2.14)
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
acts_as_list (0.2.0)
activerecord (>= 3.0)
addressable (2.3.4)
@ -245,6 +225,14 @@ GEM
rack_session_access (0.1.1)
builder (>= 2.0.0)
rack (>= 1.0.0)
rails (3.2.14)
actionmailer (= 3.2.14)
actionpack (= 3.2.14)
activerecord (= 3.2.14)
activeresource (= 3.2.14)
activesupport (= 3.2.14)
bundler (~> 1.0)
railties (= 3.2.14)
rails-dev-tweaks (0.6.1)
actionpack (~> 3.1)
railties (~> 3.1)
@ -252,6 +240,13 @@ GEM
rails (>= 3.0.0)
rails_autolink (1.1.0)
rails (> 3.1)
railties (3.2.14)
actionpack (= 3.2.14)
activesupport (= 3.2.14)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.0.4)
rb-fsevent (0.9.3)
rb-inotify (0.9.0)
@ -397,7 +392,7 @@ DEPENDENCIES
pry-rescue
pry-stack_explorer
rack_session_access
rails!
rails (~> 3.2.14)
rails-dev-tweaks (~> 0.6.1)
rails-footnotes (>= 3.7.5.rc4)
rails_autolink

@ -84,7 +84,11 @@ class ActivitiesController < ApplicationController
if event.respond_to?(:changed_data) and event.changed_data['project_id']
project_ids = event.changed_data['project_id']
elsif event.respond_to?(:project_id) or event.journaled.respond_to?(:project_id)
# if possible access project_id (its faster)
project_ids = [ event.project_id ]
elsif event.respond_to?(:project) or event.journaled.respond_to?(:project)
# sometimes (e.g.) for wikis, we have no :project_id, but a :project method.
project_ids = [ event.project.id ]
end
if project_ids.empty?
# show this event if it is not associated with a project

@ -87,8 +87,7 @@ class AdminController < ApplicationController
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [
[:text_default_administrator_account_changed,
!User.find_by_login('admin').current_password.same_as_plain_password?('admin')],
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_rmagick_available, Object.const_defined?(:Magick)]
]

@ -177,20 +177,8 @@ JS
attrs.delete(:project_id)
role_ids = attrs.delete(:role_ids).map(&:to_i).select{ |i| i > 0 }
roles = Role.find_all_by_id(role_ids)
# Keep inherited roles
role_ids += @member.member_roles.select { |mr| !mr.inherited_from.nil? }.collect(&:role_id)
new_role_ids = role_ids - @member.role_ids
# Add new roles
new_role_ids.each { |id| @member.member_roles.build.tap { |r| r.role_id = id } }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = @member.member_roles.select { |mr| !role_ids.include?(mr.role_id) }
if member_roles_to_destroy.any?
member_roles_to_destroy.each(&:mark_for_destruction)
Watcher.prune(:user => @member.principal, :project => @member.project)
end
@member.assign_roles(role_ids)
@member.attributes = attrs
@member

@ -12,14 +12,11 @@
class WorkPackagesController < ApplicationController
unloadable
helper :timelines, :planning_elements
include ExtendedHTTP
include Redmine::Export::PDF
current_menu_item do |controller|
begin
wp = controller.new_work_package || controller.work_package
wp = controller.work_package
case wp
when PlanningElement
@ -32,18 +29,10 @@ class WorkPackagesController < ApplicationController
end
end
model_object WorkPackage
before_filter :disable_api
before_filter :find_model_object_and_project, :only => [:show, :edit, :update]
before_filter :find_project_by_project_id, :only => [:new, :create]
before_filter :project, :only => [:new_type]
before_filter :authorize,
:assign_planning_elements
before_filter :apply_at_timestamp, :only => [:show]
helper :timelines
helper :timelines_journals
before_filter :not_found_unless_work_package,
:project,
:authorize
def show
respond_to do |format|
@ -83,7 +72,7 @@ class WorkPackagesController < ApplicationController
def new
respond_to do |format|
format.html { render :locals => { :work_package => new_work_package,
format.html { render :locals => { :work_package => work_package,
:project => project,
:priorities => priorities,
:user => current_user } }
@ -91,8 +80,11 @@ class WorkPackagesController < ApplicationController
end
def new_type
safe_params = permitted_params.update_work_package(:project => project)
work_package.update_by(current_user, safe_params)
respond_to do |format|
format.js { render :locals => { :work_package => work_package || new_work_package,
format.js { render :locals => { :work_package => work_package,
:project => project,
:priorities => priorities,
:user => current_user } }
@ -100,19 +92,19 @@ class WorkPackagesController < ApplicationController
end
def create
call_hook(:controller_work_package_new_before_save, { :params => params, :work_package => new_work_package })
call_hook(:controller_work_package_new_before_save, { :params => params, :work_package => work_package })
WorkPackageObserver.instance.send_notification = send_notifications?
if new_work_package.save
if work_package.save
flash[:notice] = I18n.t(:notice_successful_create)
Attachment.attach_files(new_work_package, params[:attachments])
render_attachment_warning_if_needed(new_work_package)
Attachment.attach_files(work_package, params[:attachments])
render_attachment_warning_if_needed(work_package)
call_hook(:controller_work_pacakge_new_after_save, { :params => params, :work_package => new_work_package })
call_hook(:controller_work_pacakge_new_after_save, { :params => params, :work_package => work_package })
redirect_to(work_package_path(new_work_package))
redirect_to(work_package_path(work_package))
else
respond_to do |format|
format.html { render :action => 'new' }
@ -137,7 +129,7 @@ class WorkPackagesController < ApplicationController
configure_update_notification(send_notifications?)
safe_params = permitted_params.update_work_package(:project => project)
updated = work_package.update_by(current_user, safe_params)
updated = work_package.update_by!(current_user, safe_params)
render_attachment_warning_if_needed(work_package)
@ -151,8 +143,17 @@ class WorkPackagesController < ApplicationController
end
end
def work_package
@work_package ||= begin
if params[:id]
existing_work_package
elsif params[:project_id]
new_work_package
end
end
def existing_work_package
@existing_work_package ||= begin
wp = WorkPackage.includes(:project)
.find_by_id(params[:id])
@ -165,13 +166,20 @@ class WorkPackagesController < ApplicationController
def new_work_package
@new_work_package ||= begin
params[:work_package] ||= {}
sti_type = params[:sti_type] || params[:work_package][:sti_type] || 'Issue'
project = Project.find_visible(current_user, params[:project_id])
return nil unless project
permitted = permitted_params.new_work_package(:project => project)
permitted = if params[:work_package]
permitted_params.new_work_package(:project => project)
else
params[:work_package] ||= {}
{}
end
permitted[:author] = current_user
sti_type = params[:sti_type] || params[:work_package][:sti_type] || 'Issue'
wp = case sti_type
when PlanningElement.to_s
project.add_planning_element(permitted)
@ -188,11 +196,7 @@ class WorkPackagesController < ApplicationController
end
def project
@project ||= if params[:project_id]
find_project_by_project_id
elsif work_package
work_package.project
end
@project ||= work_package.project
end
def journals
@ -202,48 +206,21 @@ class WorkPackagesController < ApplicationController
end
def ancestors
@ancestors ||= begin
case work_package
when PlanningElement
# Right now all planning_elements of a tree are part of the same project.
# That means that a user can either see all planning_elements or none.
# Thus, after access to a planning element is established (work_package) we
# currently need no extra check for the ancestors/descendants
work_package.ancestors
when Issue
work_package.ancestors.visible.includes(:type,
@ancestors ||= work_package.ancestors.visible.includes(:type,
:assigned_to,
:status,
:priority,
:fixed_version,
:project)
end
def descendants
@descendants ||= work_package.descendants.visible.includes(:type,
:assigned_to,
:status,
:priority,
:fixed_version,
:project)
else
[]
end
end
end
def descendants
@descendants ||= begin
case work_package
when PlanningElement
# Right now all planning_elements of a tree are part of the same project.
# That means that a user can either see all planning_elements or none.
# Thus, after access to a planning element is established (work_package) we
# currently need no extra check for the ancestors/descendants
work_package.descendants
when Issue
work_package.descendants.visible.includes(:type,
:assigned_to,
:status,
:priority,
:fixed_version,
:project)
else
[]
end
end
end
@ -285,6 +262,10 @@ class WorkPackagesController < ApplicationController
protected
def not_found_unless_work_package
render_404 unless work_package
end
def configure_update_notification(state = true)
JournalObserver.instance.send_notification = state
end
@ -292,20 +273,4 @@ class WorkPackagesController < ApplicationController
def send_notifications?
params[:send_notification] == '0' ? false : true
end
def assign_planning_elements
@planning_elements = @project.planning_elements.without_deleted
end
def apply_at_timestamp
return if params[:at].blank?
time = Time.at(Integer(params[:at]))
# intentionally rebuilding scope chain to avoid without_deleted scope
@planning_elements = @project.planning_elements.at_time(time)
rescue ArgumentError
render_errors(:at => 'unknown format')
end
end

@ -39,13 +39,13 @@ class Group < Principal
end
member.member_roles.each do |member_role|
user_member.member_roles.build(:role => member_role.role, :inherited_from => member_role.id)
user_member.add_role(member_role.role, member_role.id)
end
user_member.save!
else
member.member_roles.each do |member_role|
user_member.member_roles << MemberRole.new(:role => member_role.role, :inherited_from => member_role.id)
user_member.add_and_save_role(member_role.role, member_role.id)
end
end
end
@ -53,8 +53,13 @@ class Group < Principal
def user_removed(user)
members.each do |member|
MemberRole.find(:all, :include => :member,
:conditions => ["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids]).each(&:destroy)
MemberRole.find(:all,
:include => :member,
:conditions =>
["#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)",
user.id, member.member_role_ids]).each do |member_role|
member_role.member.remove_member_role_and_destroy_member_if_last(member_role)
end
end
end

@ -31,21 +31,48 @@ class Member < ActiveRecord::Base
self.user.name
end
# Set the roles for this member to the given roles_or_role_ids.
# Inherited roles are left untouched.
def assign_roles(roles_or_role_ids)
do_assign_roles(roles_or_role_ids, false)
end
alias :base_role_ids= :role_ids=
def role_ids=(arg)
ids = (arg || []).collect(&:to_i) - [0]
# Keep inherited roles
ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
new_role_ids = ids - role_ids
# Add new roles
new_role_ids.each {|id| member_roles << MemberRole.new.tap {|r| r.role_id = id } }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
if member_roles_to_destroy.any?
member_roles_to_destroy.each(&:destroy)
unwatch_from_permission_change
end
# Set the roles for this member to the given roles_or_role_ids, immediately
# save the changes and destroy the member in case no role is left.
# Inherited roles are left untouched.
def assign_and_save_roles_and_destroy_member_if_none_left(roles_or_role_ids)
do_assign_roles(roles_or_role_ids, true)
end
alias_method :role_ids=, :assign_and_save_roles_and_destroy_member_if_none_left
# Add a role to the membership
# Does not save the changes, the member must be saved afterwards for the role to be added.
def add_role(role_or_role_id, inherited_from_id=nil)
do_add_role(role_or_role_id, inherited_from_id, false)
end
# Add a role and save the change to the database
def add_and_save_role(role_or_role_id, inherited_from_id=nil)
do_add_role(role_or_role_id, inherited_from_id, true)
end
# Mark one of the member's roles for destruction
#
# Make sure to get the MemberRole instance from the member's association, otherwise the actual
# destruction on save doesn't work.
def mark_member_role_for_destruction(member_role)
do_remove_member_role(member_role, false)
end
# Remove a role from a member
# Destroys the member itself when no role is left afterwards
#
# Make sure to get the MemberRole instance from the member's association, otherwise the
# destruction of the member, when the last MemberRole is destroyed, might not work.
def remove_member_role_and_destroy_member_if_last(member_role)
do_remove_member_role(member_role, true)
end
def <=>(member)
@ -81,12 +108,66 @@ class Member < ActiveRecord::Base
protected
def destroy_if_no_roles_left!
destroy if member_roles.empty? || member_roles.all? do |member_role|
member_role.marked_for_destruction? || member_role.destroyed?
end
end
def validate_presence_of_role
if member_roles.empty?
errors.add :roles, :empty if roles.empty?
else
errors.add :roles, :empty if member_roles.all?(&:marked_for_destruction?)
errors.add :roles, :empty if member_roles.all? do |member_role|
member_role.marked_for_destruction? || member_role.destroyed?
end
end
end
def do_add_role(role_or_role_id, inherited_from_id, save_immediately)
id = (role_or_role_id.kind_of? Role) ? role_or_role_id.id : role_or_role_id
if save_immediately
member_roles << MemberRole.new.tap do |member_role|
member_role.role_id = id
member_role.inherited_from = inherited_from_id
end
else
member_roles.build.tap do |member_role|
member_role.role_id = id
member_role.inherited_from = inherited_from_id
end
end
end
# Set save_and_possibly_destroy to true to immediately save changes and destroy
# when no roles are left.
def do_assign_roles(roles_or_role_ids, save_and_possibly_destroy)
# ensure we have integer ids
ids = roles_or_role_ids.map { |r| (r.kind_of? Role) ? r.id : r.to_i }
# Keep inherited roles
ids += member_roles.select {|mr| !mr.inherited_from.nil?}.collect(&:role_id)
new_role_ids = ids - role_ids
# Add new roles
# Do this before destroying them, otherwise the Member is destroyed due to not having any
# Roles assigned via MemberRoles.
new_role_ids.each {|id| do_add_role(id, nil, save_and_possibly_destroy) }
# Remove roles (Rails' #role_ids= will not trigger MemberRole#on_destroy)
member_roles_to_destroy = member_roles.select {|mr| !ids.include?(mr.role_id)}
member_roles_to_destroy.each { |mr| do_remove_member_role(mr, save_and_possibly_destroy) }
end
def do_remove_member_role(member_role, destroy)
if destroy
member_role.destroy_for_member
destroy_if_no_roles_left!
else
member_role.mark_for_destruction
end
unwatch_from_permission_change
end
private

@ -16,7 +16,6 @@ class MemberRole < ActiveRecord::Base
after_create :add_role_to_group_users
after_destroy :remove_role_from_group_users
after_destroy :remove_member_if_empty
attr_protected :member_id, :role_id
@ -27,18 +26,31 @@ class MemberRole < ActiveRecord::Base
errors.add :role_id, :invalid if role && !role.member?
end
# Add alias, so Member can still destroy MemberRoles
# Don't call this from anywhere else, use remove_member_role on Member.
alias :destroy_for_member :destroy
# You shouldn't call this, only ActiveRecord itself is allowed to do this
# when destroying a Member. Use Member.remove_member_role to remove a role from a member.
#
# You may remove this once we have a layer above persistence that handles business logic
# and prevents or at least discourages working on persistence objects from controllers
# or unrelated business logic.
def destroy(*args)
unless caller.first =~ /has_many_association\.rb:[0-9]+:in `[^`]+delete_records'/
raise 'MemberRole.destroy called from method other than HasManyAssociation.delete_records' +
"\n on #{inspect}\n from #{caller.first} / #{caller[3]}"
else
super
end
end
def inherited?
!inherited_from.nil?
end
private
def remove_member_if_empty
if member and member.roles.empty?
member.destroy
end
end
def add_role_to_group_users
if member && member.principal.is_a?(Group)
member.principal.users.each do |user|
@ -50,11 +62,10 @@ class MemberRole < ActiveRecord::Base
m.user_id = user.id
end
user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id)
user_member.add_role(role, id)
user_member.save
else
user_member.member_roles << MemberRole.new(:role => role, :inherited_from => id)
user_member.add_and_save_role(role, id)
end
end
end
@ -62,7 +73,7 @@ class MemberRole < ActiveRecord::Base
def remove_role_from_group_users
MemberRole.all(:conditions => { :inherited_from => id }).group_by(&:member).each do |member, member_roles|
member_roles.each(&:destroy)
member_roles.each { |mr| member.remove_member_role_and_destroy_member_if_last(mr) }
if member && member.user
Watcher.prune(:user => member.user, :project => member.project)
end

@ -84,28 +84,30 @@ class PermittedParams < Struct.new(:params, :user)
def new_work_package(args = {})
permitted = permitted_attributes(:new_work_package, args)
params[:work_package].permit(*permitted)
end
permitted_params = params.require(:work_package).permit(*permitted)
def update_work_package(args = {})
permitted = permitted_attributes(:new_work_package, args)
permitted_params.merge!(custom_field_values(:work_package))
params[:work_package].permit(*permitted)
permitted_params
end
alias :update_work_package :new_work_package
def user_update_as_admin
if user.admin?
params.require(:user).permit(:firstname,
:lastname,
:mail,
:mail_notification,
:language,
:custom_field_values,
:custom_fields,
:identity_url,
:auth_source_id,
:force_password_change,
:group_ids => [])
permitted_params = params.require(:user).permit(:firstname,
:lastname,
:mail,
:mail_notification,
:language,
:custom_fields,
:identity_url,
:auth_source_id,
:force_password_change,
:group_ids => [])
permitted_params.merge!(custom_field_values(:user))
permitted_params
else
params.require(:user).permit()
end
@ -134,6 +136,20 @@ class PermittedParams < Struct.new(:params, :user)
protected
def custom_field_values(key)
# a hash of arbitrary values is not supported by strong params
# thus we do it by hand
values = params.require(key)[:custom_field_values] || {}
# only permit values following the schema
# 'id as string' => 'value as string'
values.reject!{ |k, v| k.to_i < 1 || !v.is_a?(String) }
values.empty? ?
{} :
{ "custom_field_values" => values }
end
def permitted_attributes(key, additions = {})
merged_args = { :params => params, :user => user }.merge(additions)

@ -410,6 +410,12 @@ class Project < ActiveRecord::Base
end
end
def self.find_visible(user, *args)
with_scope(:find => where(Project.visible_by(user))) do
find(*args)
end
end
def to_param
# id is used for projects with a numeric identifier (compatibility)
@to_param ||= (identifier.to_s =~ %r{^\d*$} ? id : identifier)

@ -864,6 +864,10 @@ class User < Principal
def log_failed_login_timestamp
self.last_failed_login_on = Time.now
end
def self.default_admin_account_changed?
!User.active.find_by_login('admin').try(:current_password).try(:same_as_plain_password?, 'admin')
end
end
class AnonymousUser < User

@ -319,13 +319,12 @@ class WorkPackage < ActiveRecord::Base
# TODO: move into Business Object and rename to update
# update for now is a private method defined by AR
def update_by(user, attributes)
init_journal(user, attributes.delete(:notes)) if attributes[:notes]
def update_by!(user, attributes)
raw_attachments = attributes.delete(:attachments)
add_time_entry_for(user, attributes.delete(:time_entry))
if update_attributes(attributes)
update_by(user, attributes)
if save
# as attach_files always saves an attachment right away
# it is not possible to stage attaching and check for
# valid. If this would be possible, we could check
@ -334,6 +333,19 @@ class WorkPackage < ActiveRecord::Base
end
end
def update_by(user, attributes)
init_journal(user, attributes.delete(:notes)) if attributes[:notes]
add_time_entry_for(user, attributes.delete(:time_entry))
attributes.delete(:attachments)
self.attributes = attributes
end
def is_milestone?
type && type.is_milestone?
end
# Returns an array of status that user is able to apply
def new_statuses_allowed_to(user, include_default=false)
return [] if status.nil?
@ -350,10 +362,6 @@ class WorkPackage < ActiveRecord::Base
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
end
def is_milestone?
type && type.is_milestone?
end
def self.use_status_for_done_ratio?
Setting.issue_done_ratio == 'issue_status'
end

@ -24,7 +24,13 @@ See doc/COPYRIGHT.rdoc for more details.
<%= render :partial => "timelines/general", :locals => {:timeline => @timeline, :f => f}%>
<%= render :partial => "timelines/comparison", :locals => {:timeline => @timeline}%>
<%# comparisons were removed so that they don't break for everyone once the
journalization changes start.
render :partial => "timelines/comparison", :locals => {:timeline => @timeline}
TODO enable comparisons once journalization is done.
%>
<%= render :partial => "timelines/vertical_planning_elements", :locals => {:timeline => @timeline}%>

@ -21,7 +21,7 @@ See doc/COPYRIGHT.rdoc for more details.
:multipart => true
} do |f| %>
<%= error_messages_for 'work_package' %>
<%= error_messages_for work_package %>
<div class="box">

@ -1,5 +1,8 @@
# Changelog
* `#1541` Use Rails 3.2.14 instead of Git Branch
* `#1598` Switching type of work package looses inserted data
## 3.0.0pre10
* `#1536` Fixed bug: Reposman.rb receives xml response for json request

@ -34,3 +34,14 @@ Scenario: Hide activity from Projects with disabled activity module
When I go to the overall activity page
Then I should see "project1" within "#activity"
And I should not see "project2" within "#activity"
Scenario: Hide wiki activity from Projects with disabled activity module
Given the project "project1" has 1 wiki page with the following:
| title | Project1Wiki |
Given the project "project2" has 1 wiki page with the following:
| title | Project2Wiki |
When I go to the overall activity page
And I check "Wiki edits" within "#menu-sidebar"
And I press "Apply" within "#menu-sidebar"
Then I should see "Project1Wiki" within "#activity"
And I should not see "Project2Wiki" within "#activity"

@ -54,7 +54,8 @@ Feature: Project Settings
And I uncheck "alpha" within "#member-1-roles-form"
And I click "Change" within "#member-1-roles-form"
Then there should be an error message
Then I should see "Bob Bobbit" within ".list.members"
And I should see "Bob Bobbit" within ".list.members"
And I should see "alpha" within ".list.members"
@javascript
Scenario: Changing members per page keeps us on the members tab

@ -25,6 +25,7 @@ Feature: Fields editable on work package edit
| name | prio1 |
And the role "manager" may have the following rights:
| edit_work_packages |
| view_work_packages |
| manage_subtasks |
And the project "ecookbook" has 1 version with:
| name | version1 |
@ -57,6 +58,7 @@ Feature: Fields editable on work package edit
Scenario: Going to the page and viewing timelog fields if this module is enabled
Given the role "manager" may have the following rights:
| edit_work_packages |
| view_work_packages |
| log_time |
And there are the following planning elements in project "ecookbook":

@ -10,7 +10,8 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: Logging time on work package update
Feature: Error messages are displayed
Background:
Given there is 1 user with:
| login | manager |
@ -33,7 +34,7 @@ Feature: Logging time on work package update
And I am already logged in as "manager"
@javascript
Scenario: Logging time
Scenario: Inserting a blank subject results in an error beeing shown
When I go to the edit page of the work package called "pe1"
And I follow "More"
And I fill in the following:

@ -6,6 +6,7 @@ Feature: Navigating to the work package edit page
And there is a role "manager"
And the role "manager" may have the following rights:
| edit_work_packages |
| view_work_packages |
And there is 1 project with the following:
| identifier | ecookbook |

@ -0,0 +1,75 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
Feature: Switching types of work packages
Background:
Given there is 1 project with the following:
| name | project1 |
| identifier | project1 |
And I am working in project "project1"
And the project "project1" has the following types:
| name | position |
| Bug | 1 |
| Feature | 2 |
And there is a default issuepriority with:
| name | Normal |
And there is a role "member"
And the role "member" may have the following rights:
| view_work_packages |
| edit_work_packages |
And there is 1 user with the following:
| login | bob |
| firstname | Bob |
| lastname | Bobbit |
And the user "bob" is a "member" in the project "project1"
Given the user "bob" has 1 issue with the following:
| subject | wp1 |
| description | Initial description |
| type | Bug |
And I am already logged in as "bob"
@javascript
Scenario: Switching type should keep the inserted value
When I go to the edit page of the work package "wp1"
And I fill in the following:
| Responsible | Bob Bobbit |
And I follow "More"
And I select "Feature" from "Type"
Then I should be on the edit page of the work package "wp1"
And I should see the following fields:
| Responsible | Bob Bobbit |
@javascript
Scenario: Switching type should update the presented custom fields
Given the following work package custom fields are defined:
| name | type |
| cfBug | int |
| cfFeature | int |
| cfAll | int |
And the custom field "cfBug" is activated for type "Bug"
And the custom field "cfFeature" is activated for type "Feature"
And the custom field "cfAll" is activated for type "Bug"
And the custom field "cfAll" is activated for type "Feature"
When I go to the edit page of the work package "wp1"
And I follow "More"
And I fill in the following:
| cfAll | 5 |
And I select "Feature" from "Type"
Then I should be on the edit page of the work package "wp1"
And I should see the following fields:
| cfFeature | |
| cfAll | 5 |

@ -37,10 +37,8 @@ begin
def get_plugin_features(prefix = '')
features = []
Rails.application.config.plugins_to_test_paths.each do |dir|
if File.directory?( dir )
feature_dir = Shellwords.escape(File.join(dir, 'features'))
features += [prefix, feature_dir]
end
feature_dir = Shellwords.escape(File.join(dir, 'features'))
features += [prefix, feature_dir] if File.directory?(feature_dir)
end
features
end

@ -25,153 +25,78 @@ describe WorkPackagesController do
let(:stub_project) { FactoryGirl.build_stubbed(:project, :identifier => 'test_project', :is_public => false) }
let(:stub_issue) { FactoryGirl.build_stubbed(:issue, :project_id => stub_project.id) }
let(:stub_user) { FactoryGirl.build_stubbed(:user) }
let(:stub_work_package) { double("work_package", :id => 1337, :project => stub_project).as_null_object }
let(:current_user) { FactoryGirl.create(:user) }
describe 'show.html' do
become_admin
describe 'w/o a valid planning element id' do
describe 'w/o being a member or administrator' do
become_non_member
it 'renders a 404 page' do
get 'show', :id => '1337'
def self.requires_permission_in_project(&block)
describe 'w/o the permission to see the project/work_package' do
before do
controller.stub(:work_package).and_return(nil)
response.response_code.should === 404
end
call_action
end
describe 'w/ the current user being a member' do
become_member_with_view_planning_element_permissions
it 'raises ActiveRecord::RecordNotFound errors' do
get 'show', :id => '1337'
response.response_code.should === 404
end
it 'should render a 404' do
response.response_code.should === 404
end
end
describe 'w/ a valid planning element id' do
become_admin
describe 'w/o being a member or administrator' do
become_non_member
it 'renders a 403 Forbidden page' do
get 'show', :id => planning_element.id
describe 'w/ the permission to see the project
w/ having the necessary permissions' do
response.response_code.should == 403
end
before do
controller.stub(:work_package).and_return(stub_work_package)
controller.should_receive(:authorize).and_return(true)
end
describe 'w/ the current user being a member' do
become_member_with_view_planning_element_permissions
before do
get 'show', :id => planning_element.id
end
it 'renders the show builder template' do
response.should render_template('work_packages/show', :formats => ["html"], :layout => :base)
end
end
instance_eval(&block)
end
end
describe 'show.pdf' do
become_admin
describe 'w/o a valid planning element id' do
describe 'w/o being a member or administrator' do
become_non_member
it 'renders a 404 page' do
get 'show', :format => 'pdf',
:id => '1337'
response.response_code.should === 404
end
end
describe 'w/ the current user being a member' do
become_member_with_view_planning_element_permissions
describe 'show.html' do
let(:call_action) { get('show', :id => '1337') }
it 'raises ActiveRecord::RecordNotFound errors' do
get 'show', :format => 'pdf',
:id => '1337'
requires_permission_in_project do
it 'renders the show builder template' do
call_action
response.response_code.should === 404
end
response.should render_template('work_packages/show', :formats => ["html"],
:layout => :base)
end
end
end
describe 'w/ a valid planning element id' do
become_admin
describe 'w/o being a member or administrator' do
become_non_member
it 'renders a 403 Forbidden page' do
get 'show', :format => 'pdf',
:id => planning_element.id
response.response_code.should == 403
end
end
describe 'w/ the current user being a member' do
become_member_with_view_planning_element_permissions
describe 'show.pdf' do
let(:call_action) { get('show', :format => 'pdf', :id => '1337') }
it "should respond with a pdf" do
pdf = double('pdf')
requires_permission_in_project do
it 'respond with a pdf' do
pdf = double('pdf')
expected_name = "#{planning_element.project.identifier}-#{planning_element.id}.pdf"
controller.stub!(:issue_to_pdf).and_return(pdf)
controller.should_receive(:send_data).with(pdf,
:type => 'application/pdf',
:filename => expected_name).and_call_original
get 'show', :format => 'pdf',
:id => planning_element.id
end
expected_name = "#{stub_work_package.project.identifier}-#{stub_work_package.id}.pdf"
controller.stub!(:issue_to_pdf).and_return(pdf)
controller.should_receive(:send_data).with(pdf,
:type => 'application/pdf',
:filename => expected_name).and_call_original
call_action
end
end
end
describe 'new.html' do
describe 'w/o specifying a project_id' do
before do
get 'new'
end
it 'should return 404 Not found' do
response.response_code.should == 404
end
end
describe 'w/o being a member' do
before do
get 'new', :project_id => project.id
end
it 'should return 403 Forbidden' do
response.response_code.should == 403
end
end
describe 'w/ beeing a member
w/ having the necessary permissions' do
become_member_with_permissions [:add_work_packages]
describe 'new.html' do
let(:call_action) { get('new', :format => 'html', :project_id => 5) }
requires_permission_in_project do
before do
get 'new', :project_id => project.id
call_action
end
it 'renders the new builder template' do
response.should render_template('work_packages/new', :formats => ["html"])
end
@ -179,68 +104,21 @@ describe WorkPackagesController do
response.response_code.should == 200
end
end
describe 'w/ beeing a member
w/o having the necessary permissions' do
become_member_with_permissions []
before do
get 'new', :project_id => project.id
end
it 'should return 403 Forbidden' do
response.response_code.should == 403
end
end
end
describe 'new_type.js' do
describe 'w/o specifying a project_id or an id' do
before do
xhr :get, :new_type
end
it 'should return 403 Not found' do
response.response_code.should == 403
end
end
describe 'w/o being a member' do
before do
xhr :get, :new_type, :project_id => project.id
end
it 'should return 403 Forbidden' do
response.response_code.should == 403
end
end
describe 'w/ beeing a member
w/ having the necessary permissions
w/ specifying a project_id' do
become_member_with_permissions [:add_work_packages]
describe 'new_type.js' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:call_action) { xhr :get, :new_type, :project_id => 5 }
requires_permission_in_project do
before do
xhr :get, :new_type, :project_id => project.id
end
it 'renders the new builder template' do
response.should render_template('work_packages/new_type', :formats => ["html"])
end
it 'should respond with 200 OK' do
response.response_code.should == 200
end
end
describe 'w/ beeing a member
w/ having the necessary permissions
w/ specifying an id' do
become_member_with_permissions [:view_work_packages,
:edit_work_packages]
controller.send(:permitted_params).should_receive(:update_work_package)
.with(:project => stub_project)
.and_return(wp_params)
stub_work_package.should_receive(:update_by).with(current_user, wp_params).and_return(true)
before do
xhr :get, :new_type, :id => planning_element.id
call_action
end
it 'renders the new builder template' do
@ -251,359 +129,280 @@ describe WorkPackagesController do
response.response_code.should == 200
end
end
describe 'w/ beeing a member
w/o having the necessary permissions' do
become_member_with_permissions []
before do
xhr :get, :new_type, :project_id => project.id
end
it 'should return 403 Forbidden' do
response.response_code.should == 403
end
end
end
describe 'create.html' do
describe 'w/o specifying a project_id' do
before do
post 'create'
end
it 'should return 404 Not found' do
response.response_code.should == 404
end
end
describe 'w/o being a member' do
before do
post 'create', :project_id => project.id
end
it 'should return 403 Forbidden' do
response.response_code.should == 403
end
end
describe 'create.html' do
let(:params) { { :project_id => stub_work_package.project.id,
:work_package => { } } }
describe 'w/ beeing a member
w/ having the necessary permissions
w/ having an successful save' do
let(:params) { { :project_id => project.id, :work_package => { } } }
let(:call_action) { post 'create', params }
become_member_with_permissions [:add_work_packages]
requires_permission_in_project do
before do
controller.stub!(:new_work_package).and_return(stub_issue)
stub_issue.should_receive(:save).and_return(true)
end
describe 'w/ having a successful save' do
before do
stub_work_package.should_receive(:save).and_return(true)
end
it 'redirect to show' do
post 'create', params
it 'redirect to show' do
call_action
response.should redirect_to(work_package_path(stub_issue))
end
response.should redirect_to(work_package_path(stub_work_package))
end
it 'should show a flash message' do
disable_flash_sweep
it 'should show a flash message' do
disable_flash_sweep
post 'create', params
call_action
flash[:notice].should == I18n.t(:notice_successful_create)
end
flash[:notice].should == I18n.t(:notice_successful_create)
end
it 'should attach attachments if those are provided' do
params[:attachments] = 'attachment-blubs-data'
it 'should attach attachments if those are provided' do
params[:attachments] = 'attachment-blubs-data'
Attachment.should_receive(:attach_files).with(stub_issue, params[:attachments])
controller.stub!(:render_attachment_warning_if_needed)
Attachment.should_receive(:attach_files).with(stub_work_package, params[:attachments])
controller.stub!(:render_attachment_warning_if_needed)
post 'create', params
call_action
end
end
end
describe 'w/ beeing a member
w/ having the necessary permissions
w/ having an unsuccessful save' do
become_member_with_permissions [:add_work_packages]
describe 'w/ having an unsuccessful save' do
before do
controller.stub!(:new_work_package).and_return(stub_issue)
stub_issue.should_receive(:save).and_return(false)
before do
stub_work_package.should_receive(:save).and_return(false)
post 'create', :project_id => project.id
end
call_action
end
it 'renders the new template' do
response.should render_template('work_packages/new', :formats => ["html"])
it 'renders the new template' do
response.should render_template('work_packages/new', :formats => ["html"])
end
end
end
end
describe 'w/ beeing a member
w/o having the necessary permissions' do
become_member_with_permissions []
describe 'edit.html' do
let(:call_action) { get 'edit', :id => stub_work_package.id }
before do
get 'new', :project_id => project.id
end
requires_permission_in_project do
it 'renders the show builder template' do
call_action
it 'should return 403 Forbidden' do
response.response_code.should == 403
response.should render_template('work_packages/edit', :formats => ["html"], :layout => :base)
end
end
end
describe 'edit.html' do
become_admin
describe 'w/o a valid work_package id' do
describe 'w/o being a member or administrator' do
become_non_member
it 'renders a 404 page' do
get 'edit', :id => '1337'
describe 'update.html' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:params) { { :id => stub_work_package.id, :work_package => wp_params } }
let(:call_action) { put 'update', params }
response.response_code.should === 404
end
requires_permission_in_project do
before do
controller.stub(:work_package).and_return(stub_work_package)
controller.send(:permitted_params).should_receive(:update_work_package)
.with(:project => stub_work_package.project)
.and_return(wp_params)
end
describe 'w/ the current user being a member' do
become_member_with_view_planning_element_permissions
describe 'w/ having a successful save' do
before do
stub_work_package.should_receive(:update_by!)
.with(current_user, wp_params)
.and_return(true)
end
it 'raises ActiveRecord::RecordNotFound errors' do
get 'edit', :id => '1337'
it 'should respond with 200 OK' do
call_action
response.response_code.should === 404
response.response_code.should == 200
end
end
end
describe 'w/ a valid work package id' do
become_admin
describe 'w/o being a member or administrator' do
become_non_member
it 'should show a flash message' do
disable_flash_sweep
it 'renders a 403 Forbidden page' do
get 'edit', :id => planning_element.id
call_action
response.response_code.should == 403
flash[:notice].should == I18n.t(:notice_successful_update)
end
end
describe 'w/ the current user being a member' do
become_member_with_permissions [:edit_work_packages]
describe 'w/ having an unsuccessful save' do
before do
get 'edit', :id => planning_element.id
stub_work_package.should_receive(:update_by!)
.with(current_user, wp_params)
.and_return(false)
end
it 'renders the show builder template' do
it 'render the edit action' do
call_action
response.should render_template('work_packages/edit', :formats => ["html"], :layout => :base)
end
end
end
end
describe 'update.html' do
describe 'w/o being a member' do
before do
put 'update'
end
describe 'w/ having a successful save
w/ having a faulty attachment' do
it 'should return 404 Not Found' do
response.response_code.should == 404
end
end
before do
stub_work_package.should_receive(:update_by!)
.with(current_user, wp_params)
.and_return(true)
stub_work_package.stub(:unsaved_attachments)
.and_return([double('unsaved_attachment')])
end
describe 'w/ beeing a member
w/ having the necessary permissions
w/ a valid wp id
w/ having a successful save' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:params) { { :id => planning_element.id, :work_package => wp_params } }
it 'should respond with 200 OK' do
call_action
become_member_with_permissions [:edit_work_packages]
response.response_code.should == 200
end
before do
controller.stub!(:work_package).and_return(planning_element)
controller.send(:permitted_params).should_receive(:update_work_package)
.with(:project => planning_element.project)
.and_return(wp_params)
planning_element.should_receive(:update_by).with(current_user, wp_params).and_return(true)
end
it 'should show a flash message' do
disable_flash_sweep
it 'should respond with 200 OK' do
put 'update', params
call_action
response.response_code.should == 200
flash[:warning].should == I18n.t(:warning_attachments_not_saved, :count => 1)
end
end
end
end
it 'should show a flash message' do
disable_flash_sweep
put 'update', params
describe :work_package do
describe 'when providing an id (wanting to see an existing wp)' do
describe 'when beeing allowed to see the work_package' do
become_member_with_view_planning_element_permissions
flash[:notice].should == I18n.t(:notice_successful_update)
end
end
it 'should return the work_package' do
controller.params = { id: planning_element.id }
describe 'w/ beeing a member
w/ having the necessary permissions
w/ a valid wp id
w/ having an unsuccessful save' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:params) { { :id => planning_element.id, :work_package => wp_params } }
controller.work_package.should == planning_element
end
become_member_with_permissions [:edit_work_packages]
it 'should return nil for non existing work_packages' do
controller.params = { id: 0 }
before do
controller.stub!(:work_package).and_return(planning_element)
controller.send(:permitted_params).should_receive(:update_work_package)
.with(:project => planning_element.project)
.and_return(wp_params)
planning_element.should_receive(:update_by).with(current_user, wp_params).and_return(false)
controller.work_package.should be_nil
end
end
it 'render the edit action' do
put 'update', params
describe 'when not beeing allowed to see the work_package' do
it 'should return nil' do
controller.params = { id: planning_element.id }
response.should render_template('work_packages/edit', :formats => ["html"], :layout => :base)
controller.work_package.should be_nil
end
end
end
describe 'w/ beeing a member
w/ having the necessary permissions
w/ a valid wp id
w/ having a successful save
w/ having a faulty attachment' do
describe 'when providing a project_id (wanting to build a new wp)' do
let(:wp_params) { { :wp_attribute => double('wp_attribute') } }
let(:params) { { :id => planning_element.id, :work_package => wp_params } }
become_member_with_permissions [:edit_work_packages]
let(:params) { { :project_id => stub_project.id } }
before do
controller.stub!(:work_package).and_return(planning_element)
controller.send(:permitted_params).should_receive(:update_work_package)
.with(:project => planning_element.project)
.and_return(wp_params)
planning_element.should_receive(:update_by).with(current_user, wp_params).and_return(true)
planning_element.stub(:unsaved_attachments).and_return([double('unsaved_attachment')])
Project.stub(:find_visible).and_return stub_project
end
it 'should respond with 200 OK' do
put 'update', params
describe 'when the type is "PlanningElement"' do
before do
controller.params = { :sti_type => 'PlanningElement',
:work_package => {} }.merge(params)
response.response_code.should == 200
end
controller.stub(:current_user).and_return(stub_user)
controller.send(:permitted_params).should_receive(:new_work_package)
.with(:project => stub_project)
.and_return(wp_params)
it 'should show a flash message' do
disable_flash_sweep
stub_project.should_receive(:add_planning_element) do |args|
put 'update', params
expect(args[:author]).to eql stub_user
flash[:warning].should == I18n.t(:warning_attachments_not_saved, :count => 1)
end
end
end
end.and_return(stub_planning_element)
end
describe :work_package do
describe 'when beeing allowed to see the work_package' do
become_member_with_view_planning_element_permissions
it 'should return a new planning element on the project' do
controller.work_package.should == stub_planning_element
end
it 'should return the work_package' do
controller.params = { id: planning_element.id }
it 'should copy over attributes from another work_package provided as the source' do
controller.params[:copy_from] = 2
stub_planning_element.should_receive(:copy_from).with(2, :exclude => [:project_id])
controller.work_package.should == planning_element
controller.work_package
end
end
it 'should return nil for non existing work_packages' do
controller.params = { id: 0 }
controller.work_package.should be_nil
end
end
describe 'when not beeing allowed to see the work_package' do
it 'should return nil' do
controller.params = { id: planning_element.id }
controller.work_package.should be_nil
end
end
end
describe 'when the type is "Issue"' do
before do
controller.params = { :sti_type => 'Issue',
:work_package => {} }.merge(params)
describe :new_work_package do
describe 'when the type is "PlanningElement"' do
before do
controller.params = { :sti_type => 'PlanningElement',
:work_package => {} }
controller.stub!(:project).and_return(project)
controller.stub!(:current_user).and_return(stub_user)
controller.stub(:current_user).and_return(stub_user)
controller.send(:permitted_params).should_receive(:new_work_package)
.with(:project => stub_project)
.and_return(wp_params)
project.should_receive(:add_planning_element) do |args|
stub_project.should_receive(:add_issue) do |args|
expect(args[:author]).to eql stub_user
expect(args[:author]).to eql stub_user
end.and_return(stub_planning_element)
end
end.and_return(stub_issue)
end
it 'should return a new planning element on the project' do
controller.new_work_package.should == stub_planning_element
end
it 'should return a new issue on the project' do
controller.work_package.should == stub_issue
end
it 'should copy over attributes from another work_package provided as the source' do
controller.params[:copy_from] = 2
stub_planning_element.should_receive(:copy_from).with(2, :exclude => [:project_id])
it 'should copy over attributes from another work_package provided as the source' do
controller.params[:copy_from] = 2
stub_issue.should_receive(:copy_from).with(2, :exclude => [:project_id])
controller.new_work_package
controller.work_package
end
end
end
describe 'when the type is "Issue"' do
before do
controller.params = { :sti_type => 'Issue',
:work_package => {} }
controller.stub!(:project).and_return(project)
controller.stub!(:current_user).and_return(stub_user)
project.should_receive(:add_issue) do |args|
expect(args[:author]).to eql stub_user
describe 'if the project is not visible for the current_user' do
before do
projects = [stub_project]
Project.stub(:visible).and_return projects
projects.stub(:find_by_id).and_return(stub_project)
end
end.and_return(stub_issue)
end
it 'should return nil' do
controller.work_package.should be_nil
it 'should return a new issue on the project' do
controller.new_work_package.should == stub_issue
end
end
it 'should copy over attributes from another work_package provided as the source' do
controller.params[:copy_from] = 2
stub_issue.should_receive(:copy_from).with(2, :exclude => [:project_id])
describe 'when the sti_type is "Project"' do
it "should raise not allowed" do
controller.params = { :sti_type => 'Project',
:project_id => stub_project.id }.merge(params)
controller.new_work_package
expect { controller.work_package }.to raise_error ArgumentError
end
end
end
describe 'when the type is "Project"' do
it "should raise not allowed" do
controller.params = { :sti_type => 'Project' }
describe 'when providing neither id nor project_id (error)' do
it "should return nil" do
controller.params = {}
expect { controller.new_work_package }.to raise_error ArgumentError
controller.work_package.should be_nil
end
end
end
describe :project do
it "should be the work_packages's project" do
controller.stub!(:work_package).and_return(planning_element)
controller.stub(:work_package).and_return(planning_element)
controller.project.should == project
controller.project.should == planning_element.project
end
end
@ -709,6 +508,10 @@ describe WorkPackagesController do
end
describe :descendants do
before do
controller.stub(:work_package).and_return(planning_element)
end
it "should be empty" do
controller.descendants.should be_empty
end

@ -14,13 +14,136 @@ require 'spec_helper'
describe Member do
let(:user) { FactoryGirl.create(:user) }
let(:role) { FactoryGirl.create(:role) }
let(:member) { FactoryGirl.build(:member, :user => user) }
it "fails to save members without roles" do
member.role_ids = [role.id]
member.save!
member.member_roles.each(&:mark_for_destruction)
member.should_not be_valid
member.errors[:roles].should include "can't be empty"
let(:second_role) { FactoryGirl.create(:role) }
let(:member) { FactoryGirl.create(:member, :user => user, :roles => [role]) }
describe '#add_role' do
before do
member.add_role(second_role)
member.save!
member.reload
end
context(:roles) do
it { member.roles.should include role }
it { member.roles.should include second_role }
end
end
describe '#add_and_save_role' do
before do
member.add_and_save_role(second_role)
member.reload
end
context(:roles) do
it { member.roles.should include role }
it { member.roles.should include second_role }
end
end
describe '#assign_roles' do
describe 'when replacing an existing role' do
before do
member.assign_roles([second_role])
member.save!
member.reload
end
context :roles do
it { member.roles.should_not include role }
it { member.roles.should include second_role }
end
end
describe 'when assigning empty list of roles' do
before do
member.assign_roles([])
res = member.save
end
context(:roles) { it { member.roles.should include role } }
context(:errors) { it { member.errors[:roles].should include "can't be empty" } }
end
end
describe "#assign_and_save_roles_and_destroy_member_if_none_left" do
describe 'when replacing an existing role' do
before do
member.assign_and_save_roles_and_destroy_member_if_none_left([second_role])
member.save!
member.reload
end
context :roles do
it { member.roles.should_not include role }
it { member.roles.should include second_role }
end
end
context 'when assigning an empty list of roles' do
before do
member.assign_and_save_roles_and_destroy_member_if_none_left([])
end
it('member should be destroyed') { member.destroyed?.should == true }
context(:roles) { it { member.roles(true).should be_empty } }
end
end
describe '#mark_member_role_for_destruction' do
context 'after saving the member' do
before do
# Add a second role, since we can't remove the last one
member.add_and_save_role(second_role)
member.reload
# Use member_roles(true) to make sure that all member roles are loaded,
# otherwise ActiveRecord doesn't notice mark_for_destruction.
member_role = member.member_roles(true).first
member.mark_member_role_for_destruction(member_role)
member.save!
member.reload
end
context(:roles) { it { member.roles.length.should == 1 } }
context(:member_roles) { it { member.member_roles.length.should == 1 } }
end
context 'before saving the member when removing the last role' do
before do
member_role = member.member_roles(true).first
member.mark_member_role_for_destruction(member_role)
end
context(:roles) { it { member.roles.should_not be_empty } }
context(:member_roles) { it { member.member_roles.should_not be_empty } }
context(:member) { it { member.should_not be_valid } }
end
end
describe '#remove_member_role_and_destroy_member_if_last' do
context 'when a member role remains' do
before do
# Add second role, so we can check it remains
member.add_and_save_role(second_role)
member_role = member.member_roles(true).first
member.remove_member_role_and_destroy_member_if_last(member_role)
end
it('member should not be destroyed') { member.destroyed?.should == false }
context(:roles) do
it { member.roles(true).length.should == 1 }
it { member.roles(true).first.id.should == second_role.id }
end
end
context 'when removing the last member role' do
before do
member_role = member.member_roles(true).first
member.remove_member_role_and_destroy_member_if_last(member_role)
end
it('member should be destroyed') { member.destroyed?.should == true }
context(:roles) { it { member.roles(true).should be_empty } }
end
end
end

@ -403,6 +403,22 @@ describe PermittedParams do
PermittedParams.new(params, user).new_work_package(:project => project).should == {}
end
it "should permit custom field values" do
hash = { "custom_field_values" => { "1" => "5" } }
params = ActionController::Parameters.new(:work_package => hash)
PermittedParams.new(params, user).new_work_package.should == hash
end
it "should remove custom field values that do not follow the schema 'id as string' => 'value as string'" do
hash = { "custom_field_values" => { "blubs" => "5", "5" => {"1" => "2"} } }
params = ActionController::Parameters.new(:work_package => hash)
PermittedParams.new(params, user).new_work_package.should == {}
end
end
describe :update_work_package do
@ -571,6 +587,22 @@ describe PermittedParams do
PermittedParams.new(params, user).update_work_package(:project => project).should == {}
end
it "should permit custom field values" do
hash = { "custom_field_values" => { "1" => "5" } }
params = ActionController::Parameters.new(:work_package => hash)
PermittedParams.new(params, user).new_work_package.should == hash
end
it "should remove custom field values that do not follow the schema 'id as string' => 'value as string'" do
hash = { "custom_field_values" => { "blubs" => "5", "5" => {"1" => "2"} } }
params = ActionController::Parameters.new(:work_package => hash)
PermittedParams.new(params, user).new_work_package.should == {}
end
end
describe :user do
@ -579,7 +611,6 @@ describe PermittedParams do
'mail',
'mail_notification',
'language',
'custom_field_values',
'custom_fields',
'identity_url',
'auth_source_id',
@ -609,5 +640,21 @@ describe PermittedParams do
PermittedParams.new(params, admin).user_update_as_admin.should == hash
end
it "should permit custom field values" do
hash = { "custom_field_values" => { "1" => "5" } }
params = ActionController::Parameters.new(:user => hash)
PermittedParams.new(params, admin).user_update_as_admin.should == hash
end
it "should remove custom field values that do not follow the schema 'id as string' => 'value as string'" do
hash = { "custom_field_values" => { "blubs" => "5", "5" => {"1" => "2"} } }
params = ActionController::Parameters.new(:user => hash)
PermittedParams.new(params, admin).user_update_as_admin.should == {}
end
end
end

@ -10,10 +10,14 @@
#++
require 'spec_helper'
require File.expand_path('../../support/shared/become_member', __FILE__)
describe Project do
include BecomeMember
let(:project) { FactoryGirl.build(:project) }
let(:admin) { FactoryGirl.create(:admin) }
let(:user) { FactoryGirl.create(:user) }
describe Project::STATUS_ACTIVE do
it "equals 1" do
@ -130,4 +134,26 @@ describe Project do
project.add_issue(attributes)
end
end
describe :find_visible do
it 'should find the project by id if the user is project member' do
become_member_with_permissions(project, user, :view_work_packages)
Project.find_visible(user, project.id).should == project
end
it 'should find the project by identifier if the user is project member' do
become_member_with_permissions(project, user, :view_work_packages)
Project.find_visible(user, project.identifier).should == project
end
it 'should not find the project by identifier if the user is no project member' do
expect { Project.find_visible(user, project.identifier) }.to raise_error ActiveRecord::RecordNotFound
end
it 'should not find the project by id if the user is no project member' do
expect { Project.find_visible(user, project.id) }.to raise_error ActiveRecord::RecordNotFound
end
end
end

@ -216,4 +216,47 @@ describe User do
end
end
end
describe ".default_admin_account_deleted_or_changed?" do
let(:default_admin) { FactoryGirl.build(:user, :login => 'admin', :password => 'admin', :password_confirmation => 'admin', :admin => true) }
before do
Setting.password_min_length = 5
end
context "default admin account exists with default password" do
before do
default_admin.save
end
it { User.default_admin_account_changed?.should be_false }
end
context "default admin account exists with changed password" do
before do
default_admin.update_attribute :password, 'dafaultAdminPwd'
default_admin.update_attribute :password_confirmation, 'dafaultAdminPwd'
default_admin.save
end
it { User.default_admin_account_changed?.should be_true }
end
context "default admin account was deleted" do
before do
default_admin.save
default_admin.delete
end
it { User.default_admin_account_changed?.should be_true }
end
context "default admin account was disabled" do
before do
default_admin.status = User::STATUSES[:locked]
default_admin.save
end
it { User.default_admin_account_changed?.should be_true }
end
end
end

@ -203,7 +203,7 @@ describe WorkPackage do
end
end
describe :update_with do
describe :update_by! do
#TODO remove once only WP exists
[:issue, :planning_element].each do |subclass|
@ -211,17 +211,17 @@ describe WorkPackage do
let(:instance) { send(subclass) }
it "should return true" do
instance.update_by(user, {}).should be_true
instance.update_by!(user, {}).should be_true
end
it "should set the values" do
instance.update_by(user, { :subject => "New subject" })
instance.update_by!(user, { :subject => "New subject" })
instance.subject.should == "New subject"
end
it "should create a journal with the journal's 'notes' attribute set to the supplied" do
instance.update_by(user, { :notes => "blubs" })
instance.update_by!(user, { :notes => "blubs" })
instance.journals.last.notes.should == "blubs"
end
@ -234,7 +234,7 @@ describe WorkPackage do
.with(instance, raw_attachments)
.and_return(attachment)
instance.update_by(user, { :attachments => raw_attachments })
instance.update_by!(user, { :attachments => raw_attachments })
end
it "should only attach the attachment when saving was successful" do
@ -243,13 +243,13 @@ describe WorkPackage do
Attachment.should_not_receive(:attach_files)
instance.update_by(user, { :subject => "", :attachments => raw_attachments })
instance.update_by!(user, { :subject => "", :attachments => raw_attachments })
end
it "should add a time entry" do
activity = FactoryGirl.create(:time_entry_activity)
instance.update_by(user, { :time_entry => { "hours" => "5",
instance.update_by!(user, { :time_entry => { "hours" => "5",
"activity_id" => activity.id.to_s,
"comments" => "blubs" } } )
@ -267,7 +267,7 @@ describe WorkPackage do
it "should not persist the time entry if the #{subclass}'s update fails" do
activity = FactoryGirl.create(:time_entry_activity)
instance.update_by(user, { :subject => '',
instance.update_by!(user, { :subject => '',
:time_entry => { "hours" => "5",
"activity_id" => activity.id.to_s,
"comments" => "blubs" } } )
@ -284,7 +284,7 @@ describe WorkPackage do
"activity_id" => "",
"comments" => "" }
instance.update_by(user, :time_entry => time_attributes)
instance.update_by!(user, :time_entry => time_attributes)
instance.should have(0).time_entries
end

@ -0,0 +1,21 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
require File.expand_path('../../support/permission_specs', __FILE__)
describe WorkPackagesController, "add_work_packages permission", :type => :controller do
include PermissionSpecs
check_permission_required_for('work_packages#new', :add_work_packages)
check_permission_required_for('work_packages#new_type', :add_work_packages)
check_permission_required_for('work_packages#create', :add_work_packages)
end

@ -0,0 +1,21 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
require File.expand_path('../../support/permission_specs', __FILE__)
describe WorkPackagesController, "edit_work_packages permission", :type => :controller do
include PermissionSpecs
check_permission_required_for('work_packages#edit', :edit_work_packages)
check_permission_required_for('work_packages#update', :edit_work_packages)
check_permission_required_for('work_packages#new_type', :edit_work_packages)
end

@ -0,0 +1,19 @@
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../../spec_helper', __FILE__)
require File.expand_path('../../support/permission_specs', __FILE__)
describe WorkPackagesController, "view_work_packages permission", :type => :controller do
include PermissionSpecs
check_permission_required_for('work_packages#show', :view_work_packages)
end

@ -0,0 +1,50 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require File.expand_path('../shared/become_member', __FILE__)
module PermissionSpecs
def self.included(base)
base.class_eval do
let(:project) { FactoryGirl.create(:project, :is_public => false) }
let(:current_user) { FactoryGirl.create(:user) }
include BecomeMember
def self.check_permission_required_for(controller_action, permission)
controller_name, action_name = controller_action.split('#')
it "should allow calling #{controller_action} when having the permission #{permission} permission" do
become_member_with_permissions(project, current_user, permission)
controller.send(:authorize, controller_name, action_name).should be_true
end
it "should prevent calling #{controller_action} when not having the permission #{permission} permission" do
become_member_with_permissions(project, current_user)
controller.send(:authorize, controller_name, action_name).should be_false
end
end
before do
# As failures generate a response we need to prevent calls to nil
controller.response = ActionController::TestResponse.new
User.stub(:current).and_return(current_user)
controller.instance_variable_set(:@project, project)
end
end
end
end

@ -0,0 +1,29 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
#
# Copyright (C) 2012-2013 the OpenProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module BecomeMember
def self.included(base)
base.send(:include, InstanceMethods)
end
module InstanceMethods
def become_member_with_permissions(project, user, permissions = [])
permissions = Array(permissions)
role = FactoryGirl.create(:role, :permissions => permissions)
member = FactoryGirl.build(:member, :user => user, :project => project)
member.roles = [role]
member.save!
end
end
end
Loading…
Cancel
Save