Merge branch 'release/4.3' into dev

pull/3302/head
Oliver Günther 9 years ago
commit 108160ed88
  1. 7
      app/controllers/repositories_controller.rb
  2. 2
      app/controllers/work_packages/bulk_controller.rb
  3. 2
      app/controllers/work_packages_controller.rb
  4. 5
      app/helpers/repositories_helper.rb
  5. 5
      app/models/comment_observer.rb
  6. 10
      app/models/journal_manager.rb
  7. 87
      app/models/journal_notification_mailer.rb
  8. 67
      app/models/journal_observer.rb
  9. 3
      app/models/message_observer.rb
  10. 3
      app/models/news_observer.rb
  11. 4
      app/models/project.rb
  12. 35
      app/models/repository.rb
  13. 6
      app/models/repository/git.rb
  14. 6
      app/models/repository/subversion.rb
  15. 56
      app/models/wiki_content.rb
  16. 10
      app/models/wiki_content_observer.rb
  17. 5
      app/models/work_package.rb
  18. 2
      app/services/create_work_package_service.rb
  19. 2
      app/services/update_work_package_service.rb
  20. 2
      app/views/repositories/settings/_vendor_form.html.erb
  21. 2
      app/workers/scm/create_repository_job.rb
  22. 7
      config/application.rb
  23. 22
      config/configuration.yml.example
  24. 10
      config/initializers/subscribe_listeners.rb
  25. 2
      config/locales/en.yml
  26. 21
      db/migrate/20150716163704_remove_filesystem_repositories.rb
  27. 4
      db/seeds/development.rb
  28. 8
      doc/apiv3-documentation.apib
  29. 12
      frontend/tests/integration/specs/work-packages/work-packages-spec.js
  30. 77
      lib/api/v3/repositories/revision_representer.rb
  31. 61
      lib/api/v3/repositories/revisions_api.rb
  32. 52
      lib/api/v3/repositories/revisions_by_work_package_api.rb
  33. 37
      lib/api/v3/repositories/revisions_collection_representer.rb
  34. 1
      lib/api/v3/root.rb
  35. 8
      lib/api/v3/utilities/path_helper.rb
  36. 6
      lib/api/v3/work_packages/work_package_representer.rb
  37. 1
      lib/api/v3/work_packages/work_packages_api.rb
  38. 6
      lib/open_project/scm/adapters/base.rb
  39. 2
      lib/open_project/scm/adapters/git.rb
  40. 19
      lib/open_project/scm/adapters/local_client.rb
  41. 4
      lib/open_project/scm/adapters/subversion.rb
  42. 25
      lib/open_project/scm/manageable_repository.rb
  43. 5
      lib/plugins/acts_as_event/lib/acts_as_event.rb
  44. 3
      lib/plugins/acts_as_journalized/lib/redmine/acts/journalized/deprecated.rb
  45. 11
      lib/plugins/acts_as_journalized/lib/redmine/acts/journalized/save_hooks.rb
  46. 6
      lib/plugins/acts_as_watchable/lib/acts_as_watchable.rb
  47. 4
      spec/features/boards/message_spec.rb
  48. 2
      spec/features/repositories/create_repository_spec.rb
  49. 14
      spec/features/work_packages/details/activity_comments_spec.rb
  50. 3
      spec/features/work_packages/work_packages_page.rb
  51. 4
      spec/legacy/unit/comment_spec.rb
  52. 133
      spec/legacy/unit/journal_observer_spec.rb
  53. 2
      spec/legacy/unit/journal_spec.rb
  54. 6
      spec/legacy/unit/user_spec.rb
  55. 4
      spec/legacy/unit/watcher_spec.rb
  56. 127
      spec/lib/api/v3/repositories/revision_representer_spec.rb
  57. 18
      spec/lib/api/v3/utilities/path_helper_spec.rb
  58. 20
      spec/lib/api/v3/work_packages/work_package_representer_spec.rb
  59. 2
      spec/lib/open_project/scm/adapters/git_adapter_spec.rb
  60. 2
      spec/lib/open_project/scm/adapters/subversion_adapter_spec.rb
  61. 156
      spec/models/journal_notification_mailer_spec.rb
  62. 26
      spec/models/repository/git_spec.rb
  63. 22
      spec/models/repository/subversion_spec.rb
  64. 21
      spec/models/work_package/work_package_action_mailer_spec.rb
  65. 4
      spec/models/work_package/work_package_copy_spec.rb
  66. 4
      spec/models/work_package_spec.rb
  67. 98
      spec/requests/api/v3/repositories/revisions_by_work_package_resource_spec.rb
  68. 97
      spec/requests/api/v3/repositories/revisions_resource_spec.rb
  69. 2
      spec/support/shared/acts_as_watchable.rb

@ -109,13 +109,8 @@ class RepositoriesController < ApplicationController
end end
def destroy def destroy
if @project.repository.manageable?
flash[:notice] = I18n.t('repositories.delete_sucessful')
else
flash[:warning] = I18n.t('repositories.errors.unlink_failed_unmanageable')
end
@project.repository.destroy @project.repository.destroy
flash[:notice] = I18n.t('repositories.delete_sucessful')
redirect_to settings_repository_tab_path redirect_to settings_repository_tab_path
end end

@ -61,7 +61,7 @@ class WorkPackages::BulkController < ApplicationController
work_package.assign_attributes attributes work_package.assign_attributes attributes
call_hook(:controller_work_package_bulk_before_save, params: params, work_package: work_package) call_hook(:controller_work_package_bulk_before_save, params: params, work_package: work_package)
JournalObserver.instance.send_notification = params[:send_notification] == '0' ? false : true JournalManager.send_notification = params[:send_notification] == '0' ? false : true
unless work_package.save unless work_package.save
unsaved_work_package_ids << work_package.id unsaved_work_package_ids << work_package.id
end end

@ -133,7 +133,7 @@ class WorkPackagesController < ApplicationController
def create def create
call_hook(:controller_work_package_new_before_save, params: params, work_package: work_package) call_hook(:controller_work_package_new_before_save, params: params, work_package: work_package)
WorkPackageObserver.instance.send_notification = send_notifications? JournalManager.send_notification = send_notifications?
work_package.attach_files(params[:attachments]) work_package.attach_files(params[:attachments])

@ -188,10 +188,15 @@ module RepositoriesHelper
scms = OpenProject::Scm::Manager.enabled scms = OpenProject::Scm::Manager.enabled
vendor = repository.nil? ? nil : repository.vendor vendor = repository.nil? ? nil : repository.vendor
## Set selected vendor
if vendor && !repository.new_record? if vendor && !repository.new_record?
scms[vendor] = vendor scms[vendor] = vendor
end end
# Remove repositories that were configured to have no
# available types left.
scms.reject! { |_, klass| klass.available_types.empty? }
scms = [default_selected_option] + scms.keys scms = [default_selected_option] + scms.keys
options_for_select(scms, vendor) options_for_select(scms, vendor)
end end

@ -29,13 +29,12 @@
class CommentObserver < ActiveRecord::Observer class CommentObserver < ActiveRecord::Observer
def after_create(comment) def after_create(comment)
return unless Notifier.notify?(:news_comment_added) return unless Setting.notified_events.include?('news_comment_added')
if comment.commented.is_a?(News) if comment.commented.is_a?(News)
news = comment.commented news = comment.commented
recipients = news.recipients + news.watcher_recipients recipients = news.recipients + news.watcher_recipients
users = User.find_all_by_mails(recipients) recipients.uniq.each do |user|
users.each do |user|
UserMailer.news_comment_added(user, comment, User.current).deliver UserMailer.news_comment_added(user, comment, User.current).deliver
end end
end end

@ -28,6 +28,12 @@
#++ #++
class JournalManager class JournalManager
class << self
attr_accessor :send_notification
end
self.send_notification = true
def self.is_journalized?(obj) def self.is_journalized?(obj)
not obj.nil? and obj.respond_to? :journals not obj.nil? and obj.respond_to? :journals
end end
@ -268,4 +274,8 @@ class JournalManager
journal.customizable_journals.build custom_field_id: cv.custom_field_id, value: cv.value journal.customizable_journals.build custom_field_id: cv.custom_field_id, value: cv.value
end end
end end
def self.reset_notification
@send_notification = true
end
end end

@ -0,0 +1,87 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class JournalNotificationMailer
class << self
def distinguish_journals(journal, send_notification)
if send_notification
if journal.journable_type == 'WorkPackage' && journal.initial?
handle_work_package_create(journal.journable)
elsif journal.journable_type == 'WorkPackage'
handle_work_package_update(journal)
end
end
end
def handle_work_package_create(work_package)
if Setting.notified_events.include?('work_package_added')
notification_receivers(work_package).uniq.each do |user|
job = DeliverWorkPackageCreatedJob.new(user.id, work_package.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
def handle_work_package_update(journal)
if send_update_notification?(journal)
work_package = journal.journable
notification_receivers(work_package).uniq.each do |user|
job = DeliverWorkPackageUpdatedJob.new(user.id, journal.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
def send_update_notification?(journal)
Setting.notified_events.include?('work_package_updated') ||
notify_for_notes?(journal) ||
notify_for_status?(journal) ||
notify_for_priority(journal)
end
def notify_for_notes?(journal)
Setting.notified_events.include?('work_package_note_added') && journal.notes.present?
end
def notify_for_status?(journal)
Setting.notified_events.include?('status_updated') &&
journal.changed_data.has_key?(:status_id)
end
def notify_for_priority(journal)
Setting.notified_events.include?('work_package_priority_updated') &&
journal.changed_data.has_key?(:priority_id)
end
def notification_receivers(work_package)
work_package.recipients + work_package.watcher_recipients
end
end
end

@ -1,67 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
class JournalObserver < ActiveRecord::Observer
attr_accessor :send_notification
def after_create(journal)
if journal.journable_type == 'WorkPackage' and !journal.initial? and send_notification
after_create_issue_journal(journal)
end
clear_notification
end
def after_create_issue_journal(journal)
if Setting.notified_events.include?('work_package_updated') ||
(Setting.notified_events.include?('work_package_note_added') && journal.notes.present?) ||
(Setting.notified_events.include?('status_updated') && journal.changed_data.has_key?(:status_id)) ||
(Setting.notified_events.include?('work_package_priority_updated') && journal.changed_data.has_key?(:priority_id))
issue = journal.journable
recipients = issue.recipients + issue.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
job = DeliverWorkPackageUpdatedJob.new(user.id, journal.id, User.current.id)
Delayed::Job.enqueue job
end
end
end
# Wrap send_notification so it defaults to true, when it's nil
def send_notification
return true if @send_notification.nil?
@send_notification
end
private
# Need to clear the notification setting after each usage otherwise it might be cached
def clear_notification
@send_notification = true
end
end

@ -33,8 +33,7 @@ class MessageObserver < ActiveRecord::Observer
recipients = message.recipients recipients = message.recipients
recipients += message.root.watcher_recipients recipients += message.root.watcher_recipients
recipients += message.board.watcher_recipients recipients += message.board.watcher_recipients
users = User.find_all_by_mails(recipients.uniq) recipients.uniq.each do |user|
users.each do |user|
UserMailer.message_posted(user, message, User.current).deliver UserMailer.message_posted(user, message, User.current).deliver
end end
end end

@ -30,8 +30,7 @@
class NewsObserver < ActiveRecord::Observer class NewsObserver < ActiveRecord::Observer
def after_create(news) def after_create(news)
if Setting.notified_events.include?('news_added') if Setting.notified_events.include?('news_added')
users = User.find_all_by_mails(news.recipients) news.recipients.uniq.each do |user|
users.each do |user|
UserMailer.news_added(user, news, User.current).deliver UserMailer.news_added(user, news, User.current).deliver
end end
end end

@ -616,9 +616,9 @@ class Project < ActiveRecord::Base
possible_responsible_members.map(&:principal).compact.sort possible_responsible_members.map(&:principal).compact.sort
end end
# Returns the mail adresses of users that should be always notified on project events # Returns users that should be always notified on project events
def recipients def recipients
notified_users.map(&:mail) notified_users
end end
# Returns the users that should be notified on project events # Returns the users that should be notified on project events

@ -87,12 +87,22 @@ class Repository < ActiveRecord::Base
@scm @scm
end end
def vendor def self.scm_config
self.class.vendor scm_adapter_class.config
end
def self.available_types
supported_types - disabled_types
end
##
# Retrieves the :disabled_types setting from `configuration.yml
def self.disabled_types
scm_config[:disabled_types] || []
end end
def supported_types def vendor
[] self.class.vendor
end end
def supports_cat? def supports_cat?
@ -282,7 +292,8 @@ class Repository < ActiveRecord::Base
repository = klass.new(args) repository = klass.new(args)
repository.attributes = args repository.attributes = args
repository.project = project repository.project = project
repository.scm_type = type
set_verified_type!(repository, type) unless type.nil?
repository.configure(type, args) repository.configure(type, args)
@ -304,6 +315,20 @@ class Repository < ActiveRecord::Base
end end
end end
##
# Verifies that the chosen scm type can be selected
def self.set_verified_type!(repository, type)
if repository.class.available_types.include? type
repository.scm_type = type
else
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.errors.disabled_or_unknown_type',
type: type,
vendor: repository.vendor)
)
end
end
## ##
# Allow global permittible params. May be overridden by plugins # Allow global permittible params. May be overridden by plugins
def self.permitted_params(params) def self.permitted_params(params)

@ -38,7 +38,7 @@ class Repository::Git < Repository
end end
def configure(scm_type, _args) def configure(scm_type, _args)
if scm_type == MANAGED_TYPE if scm_type == self.class.managed_type
unless manageable? unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new( raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable') I18n.t('repositories.managed.error_not_manageable')
@ -54,9 +54,9 @@ class Repository::Git < Repository
super(params) super(params)
end end
def supported_types def self.supported_types
types = [:local] types = [:local]
types << MANAGED_TYPE if manageable? types << managed_type if manageable?
types types
end end

@ -39,7 +39,7 @@ class Repository::Subversion < Repository
end end
def configure(scm_type, _args) def configure(scm_type, _args)
if scm_type == MANAGED_TYPE if scm_type == self.class.managed_type
unless manageable? unless manageable?
raise OpenProject::Scm::Exceptions::RepositoryBuildError.new( raise OpenProject::Scm::Exceptions::RepositoryBuildError.new(
I18n.t('repositories.managed.error_not_manageable') I18n.t('repositories.managed.error_not_manageable')
@ -55,9 +55,9 @@ class Repository::Subversion < Repository
super(params).merge(params.permit(:login, :password)) super(params).merge(params.permit(:login, :password))
end end
def supported_types def self.supported_types
types = [:existing] types = [:existing]
types << MANAGED_TYPE if manageable? types << managed_type if manageable?
types types
end end

@ -68,8 +68,7 @@ class WikiContent < ActiveRecord::Base
# Returns the mail adresses of users that should be notified # Returns the mail adresses of users that should be notified
def recipients def recipients
notified = project.notified_users notified = project.notified_users
notified.reject! { |user| !visible?(user) } notified.select { |user| visible?(user) }
notified.map(&:mail)
end end
# FIXME: Deprecate # FIXME: Deprecate
@ -87,57 +86,4 @@ class WikiContent < ActiveRecord::Base
def comments_to_journal_notes def comments_to_journal_notes
add_journal author, comments add_journal author, comments
end end
# FIXME: This is for backwards compatibility only. Remove once we decide it is not needed anymore
# WikiContentJournal.class_eval do
# attr_protected :data
# after_save :compress_version_text
#
# # Wiki Content might be large and the data should possibly be compressed
# def compress_version_text
# self.text = changed_data["text"].last if changed_data["text"]
# self.text ||= self.journaled.text
# end
#
# def text=(plain)
# case Setting.wiki_compression
# when "gzip"
# begin
# text_hash :text => Zlib::Deflate.deflate(plain, Zlib::BEST_COMPRESSION), :compression => Setting.wiki_compression
# rescue
# text_hash :text => plain, :compression => ''
# end
# else
# text_hash :text => plain, :compression => ''
# end
# plain
# end
#
# def text_hash(hash)
# changed_data.delete("text")
# changed_data["data"] = hash[:text]
# changed_data["compression"] = hash[:compression]
# update_attribute(:changed_data, changed_data)
# end
#
# def text
# @text ||= case changed_data["compression"]
# when "gzip"
# Zlib::Inflate.inflate(changed_data["data"])
# else
# # uncompressed data
# changed_data["data"]
# end
# end
#
# # Returns the previous version or nil
# def previous
# @previous ||= journaled.journals.at(version - 1)
# end
#
# # FIXME: Deprecate
# def versioned
# journaled
# end
# end
end end

@ -31,8 +31,7 @@ class WikiContentObserver < ActiveRecord::Observer
def after_create(wiki_content) def after_create(wiki_content)
if Setting.notified_events.include?('wiki_content_added') if Setting.notified_events.include?('wiki_content_added')
recipients = wiki_content.recipients + wiki_content.page.wiki.watcher_recipients recipients = wiki_content.recipients + wiki_content.page.wiki.watcher_recipients
users = User.find_all_by_mails(recipients.uniq) recipients.uniq.each do |user|
users.each do |user|
UserMailer.wiki_content_added(user, wiki_content, User.current).deliver UserMailer.wiki_content_added(user, wiki_content, User.current).deliver
end end
end end
@ -40,9 +39,10 @@ class WikiContentObserver < ActiveRecord::Observer
def after_update(wiki_content) def after_update(wiki_content)
if wiki_content.text_changed? && Setting.notified_events.include?('wiki_content_updated') if wiki_content.text_changed? && Setting.notified_events.include?('wiki_content_updated')
recipients = wiki_content.recipients + wiki_content.page.wiki.watcher_recipients + wiki_content.page.watcher_recipients recipients = wiki_content.recipients
users = User.find_all_by_mails(recipients.uniq) recipients += wiki_content.page.wiki.watcher_recipients
users.each do |user| recipients += wiki_content.page.watcher_recipients
recipients.uniq.each do |user|
UserMailer.wiki_content_updated(user, wiki_content, User.current).deliver UserMailer.wiki_content_updated(user, wiki_content, User.current).deliver
end end
end end

@ -490,7 +490,7 @@ class WorkPackage < ActiveRecord::Base
end end
# >>> issues.rb >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # >>> issues.rb >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Returns the mail addresses of users that should be notified # Returns users that should be notified
def recipients def recipients
notified = project.notified_users notified = project.notified_users
# Author and assignee are always notified unless they have been # Author and assignee are always notified unless they have been
@ -505,8 +505,7 @@ class WorkPackage < ActiveRecord::Base
end end
notified.uniq! notified.uniq!
# Remove users that can not view the issue # Remove users that can not view the issue
notified.reject! { |user| !visible?(user) } notified.select { |user| visible?(user) }
notified.map(&:mail)
end end
def done_ratio def done_ratio

@ -34,7 +34,7 @@ class CreateWorkPackageService
self.user = user self.user = user
self.project = project self.project = project
WorkPackageObserver.instance.send_notification = send_notifications JournalManager.send_notification = send_notifications
end end
def create def create

@ -35,7 +35,7 @@ class UpdateWorkPackageService
self.work_package = work_package self.work_package = work_package
self.permitted_params = permitted_params self.permitted_params = permitted_params
JournalObserver.instance.send_notification = send_notifications JournalManager.send_notification = send_notifications
end end
def update def update

@ -27,7 +27,7 @@ See doc/COPYRIGHT.rdoc for more details.
++#%> ++#%>
<% if repository.new_record? %> <% if repository.new_record? %>
<% scm_types = repository.supported_types %> <% scm_types = repository.class.available_types %>
<div class="attributes-group -toggleable" data-switch="scm_type"> <div class="attributes-group -toggleable" data-switch="scm_type">
<% scm_types.each do |type|%> <% scm_types.each do |type|%>
<%= render partial: "/repositories/settings/vendor_attribute_groups", <%= render partial: "/repositories/settings/vendor_attribute_groups",

@ -78,7 +78,7 @@ class Scm::CreateRepositoryJob
end end
def config def config
@config ||= repository.scm.config @config ||= repository.class.scm_config
end end
def repository def repository

@ -94,9 +94,10 @@ module OpenProject
# Activate observers that should always be running. # Activate observers that should always be running.
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
config.active_record.observers = :journal_observer, :message_observer, config.active_record.observers = :message_observer,
:news_observer, :wiki_content_observer, :news_observer,
:comment_observer, :work_package_observer :wiki_content_observer,
:comment_observer
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.

@ -221,17 +221,33 @@ default:
# Enable managed repositories for this vendor. This allows OpenProject to # Enable managed repositories for this vendor. This allows OpenProject to
# take control over the given path to create and delete repositories directly # take control over the given path to create and delete repositories directly
# when created in the frontend. # when created in the frontend.
# NOTE: Disabling :managed repositories using disabled_types takes precedence over this setting.
# disabled_types:
# Disable specific repository types for this particular vendor. This allows
# to restrict the available choices a project administrator has for creating repositories
# See the example below for available types
#
# Available types for git:
# - :local (Local repositories, registered using a local path)
# - :managed (Managed repositores, available IF :manages path is set below)
# Available types for subversion:
# - :existing (Existing subversion repositories by URL - local using file:/// or remote
# using one of the supported URL schemes (e.g., https://, svn+ssh:// )
# - :managed (Managed repositores, available IF :manages path is set below)
# #
# Examplary configuration # Examplary configuration
# #
# scm: # scm:
# Git: # Git:
# client_command: /usr/local/bin/git # client_command: /usr/local/bin/git
# manages: /tmp/git # disabled_types:
# - :local
# manages: /opt/repositories/git
# Subversion: # Subversion:
# # Use command below to override the default svn command taken from path.
# client_command: /usr/local/bin/svn # client_command: /usr/local/bin/svn
# manages: /tmp/svn # disabled_types:
# - :existing
# manages: /opt/repositories/svn
# Key used to encrypt sensitive data in the database (SCM and LDAP passwords). # Key used to encrypt sensitive data in the database (SCM and LDAP passwords).
# If you don't want to enable data encryption, just leave it blank. # If you don't want to enable data encryption, just leave it blank.

@ -27,12 +27,6 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
module Notifier OpenProject::Notifications.subscribe('journal_created') do |payload|
def self.notify?(event) JournalNotificationMailer.distinguish_journals(payload[:journal], payload[:send_notification])
notified_events.include?(event.to_s)
end
def self.notified_events
Setting.notified_events.to_a
end
end end

@ -1267,8 +1267,8 @@ en:
path_permission_failed: "An error occurred trying to create the following path: %{path}. Please ensure that OpenProject may write to that folder." path_permission_failed: "An error occurred trying to create the following path: %{path}. Please ensure that OpenProject may write to that folder."
unauthorized: "You're not authorized to access the repository or the credentials are invalid." unauthorized: "You're not authorized to access the repository or the credentials are invalid."
unavailable: "The repository is unavailable." unavailable: "The repository is unavailable."
unlink_failed_unmanageable: "The repository was deleted, but its directory could not be removed, because it is no longer manageable by OpenProject."
exception_title: "Cannot access the repository: %{message}" exception_title: "Cannot access the repository: %{message}"
disabled_or_unknown_type: "The selected type %{type} is disabled or no longer available for the SCM vendor %{vendor}."
disabled_or_unknown_vendor: "The SCM vendor %{vendor} is disabled or no longer available." disabled_or_unknown_vendor: "The SCM vendor %{vendor} is disabled or no longer available."
git: git:
instructions: instructions:

@ -30,12 +30,31 @@
# Removes all remaining Repository::Filesystem entries. # Removes all remaining Repository::Filesystem entries.
# #
class RemoveFilesystemRepositories < ActiveRecord::Migration class RemoveFilesystemRepositories < ActiveRecord::Migration
include Migration::Utils
def up def up
ActiveRecord::Base.transaction do
# Delete any changesets belonging to filesystem repositories
ActiveRecord::Base.connection.execute <<-SQL
DELETE FROM changesets
WHERE repository_id IN (
SELECT id
FROM repositories
WHERE type = #{filesystem_type}
)
SQL
# Circumvent Repository.where(...) since this tries to load # Circumvent Repository.where(...) since this tries to load
# the Repository::Filesystem constant and fails. # the Repository::Filesystem constant and fails.
ActiveRecord::Base.connection.execute <<-SQL ActiveRecord::Base.connection.execute <<-SQL
DELETE FROM repositories DELETE FROM repositories
WHERE type = #{quote_value("Repository::Filesystem")} WHERE type = #{filesystem_type}
SQL SQL
end end
end end
def filesystem_type
quote_value('Repository::Filesystem')
end
end

@ -107,6 +107,10 @@ time_entry_activities = []
time_entry_activities << time_entry_activity time_entry_activities << time_entry_activity
end end
repository = Repository::Subversion.create!(project: project,
url: 'file:///tmp/foo/bar.svn',
scm_type: 'existing')
print 'Creating objects for...' print 'Creating objects for...'
user_count.times do |count| user_count.times do |count|
login = "#{Faker::Name.first_name}#{rand(10000)}" login = "#{Faker::Name.first_name}#{rand(10000)}"

@ -4108,7 +4108,7 @@ Gets a list of revisions that are linked to this work package, e.g., because it
**Required permission:** view changesets of the project this work package is contained in **Required permission:** view changesets of the project this work package is contained in
*Note that you will only receive this error, if you are at least allowed to see the corresponding project.* *Note that you will only receive this error, if you are at least allowed to see the corresponding work package.*
+ Body + Body
@ -4120,16 +4120,16 @@ Gets a list of revisions that are linked to this work package, e.g., because it
+ Response 404 (application/hal+json) + Response 404 (application/hal+json)
Returned if the project does not exist or the client does not have sufficient permissions to see it. Returned if the work package does not exist or the client does not have sufficient permissions to see it.
**Required permission:** view project **Required permission:** view work package
+ Body + Body
{ {
"_type": "Error", "_type": "Error",
"errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound", "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
"message": "The specified project does not exist." "message": "The specified work project does not exist."
} }

@ -38,17 +38,10 @@ describe('OpenProject', function() {
}); });
it('should show work packages title', function() { it('should show work packages title', function() {
page.get();
expect(page.getSelectableTitle().getText()).to.eventually.equal('Work packages'); expect(page.getSelectableTitle().getText()).to.eventually.equal('Work packages');
}); });
it('should show work packages', function() { it('should show work packages', function() {
page.get();
page.getTableHeaders().map(function(heading) {
return heading.getText();
}).then(function(headingTexts) {
var expected = ['', var expected = ['',
'ID', 'ID',
'TYPE', 'TYPE',
@ -56,8 +49,9 @@ describe('OpenProject', function() {
'SUBJECT', 'SUBJECT',
'ASSIGNEE']; 'ASSIGNEE'];
for (var i = 0; i < expected.length; i++) { page.getTableHeaders().then(function(headings) {
expect(headingTexts[i]).to.equal(expected[i]); for (var i = 0; i < headings.length; i++) {
expect(headings[i].getText()).to.eventually.equal(expected[i]);
} }
}); });
}); });

@ -0,0 +1,77 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Repositories
class RevisionRepresenter < ::API::Decorators::Single
include API::V3::Utilities
self_link path: :revision,
title_getter: -> (*) { nil }
link :project do
{
href: api_v3_paths.project(represented.project.id),
title: represented.project.name
}
end
link :author do
{
href: api_v3_paths.user(represented.user.id),
title: represented.user.name
} unless represented.user.nil?
end
property :id
property :identifier
property :author, as: :authorName
property :message,
exec_context: :decorator,
getter: -> (*) {
::API::Decorators::Formattable.new(represented.comments,
object: represented,
format: 'plain')
},
render_nil: true
property :created_at,
exec_context: :decorator,
getter: -> (*) {
datetime_formatter.format_datetime(represented.committed_on)
}
def _type
'Revision'
end
end
end
end
end

@ -0,0 +1,61 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Repositories
class RevisionsAPI < ::API::OpenProjectAPI
resources :revisions do
params do
requires :id, desc: 'Revision id'
end
route_param :id do
helpers do
attr_reader :revision
def revision_representer
RevisionRepresenter.new(revision)
end
end
before do
@revision = Changeset.find(params[:id])
authorize(:view_changesets, context: revision.project) do
raise API::Errors::NotFound.new
end
end
get do
revision_representer
end
end
end
end
end
end
end

@ -0,0 +1,52 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/repositories/revisions_collection_representer'
module API
module V3
module Repositories
class RevisionsByWorkPackageAPI < ::API::OpenProjectAPI
resources :revisions do
before do
authorize(:view_changesets, context: work_package.project)
end
get do
self_path = api_v3_paths.work_package_revisions(work_package.id)
revisions = work_package.changesets
RevisionsCollectionRepresenter.new(revisions,
revisions.count,
self_path)
end
end
end
end
end
end

@ -27,39 +27,12 @@
# See doc/COPYRIGHT.rdoc for more details. # See doc/COPYRIGHT.rdoc for more details.
#++ #++
class WorkPackageObserver < ActiveRecord::Observer module API
attr_accessor :send_notification module V3
module Repositories
def after_create(work_package) class RevisionsCollectionRepresenter < ::API::Decorators::Collection
if send_notification element_decorator ::API::V3::Repositories::RevisionRepresenter
recipients = work_package.recipients + work_package.watcher_recipients
users = User.find_all_by_mails(recipients.uniq)
users.each do |user|
notify(user, work_package)
end
end
clear_notification
end
##
# Notifies the user of the created work package.
def notify(user, work_package)
job = DeliverWorkPackageCreatedJob.new(user.id, work_package.id, User.current.id)
Delayed::Job.enqueue job
end end
# Wrap send_notification so it defaults to true, when it's nil
def send_notification
return true if @send_notification.nil?
@send_notification
end end
private
# Need to clear the notification setting after each usage otherwise it might be cached
def clear_notification
@send_notification = true
end end
end end

@ -42,6 +42,7 @@ module API
mount ::API::V3::Projects::ProjectsAPI mount ::API::V3::Projects::ProjectsAPI
mount ::API::V3::Queries::QueriesAPI mount ::API::V3::Queries::QueriesAPI
mount ::API::V3::Render::RenderAPI mount ::API::V3::Render::RenderAPI
mount ::API::V3::Repositories::RevisionsAPI
mount ::API::V3::Statuses::StatusesAPI mount ::API::V3::Statuses::StatusesAPI
mount ::API::V3::StringObjects::StringObjectsAPI mount ::API::V3::StringObjects::StringObjectsAPI
mount ::API::V3::Types::TypesAPI mount ::API::V3::Types::TypesAPI

@ -114,6 +114,10 @@ module API
"#{root}/relations/#{id}" "#{root}/relations/#{id}"
end end
def self.revision(id)
"#{root}/revisions/#{id}"
end
def self.render_markup(format: nil, link: nil) def self.render_markup(format: nil, link: nil)
format = format || Setting.text_formatting format = format || Setting.text_formatting
format = 'plain' if format == '' # Setting will return '' for plain format = 'plain' if format == '' # Setting will return '' for plain
@ -200,6 +204,10 @@ module API
"#{work_package_relations(work_package_id)}/#{id}" "#{work_package_relations(work_package_id)}/#{id}"
end end
def self.work_package_revisions(id)
"#{work_package(id)}/revisions"
end
def self.work_package_schema(project_id, type_id) def self.work_package_schema(project_id, type_id)
"#{root}/work_packages/schemas/#{project_id}-#{type_id}" "#{root}/work_packages/schemas/#{project_id}-#{type_id}"
end end

@ -128,6 +128,12 @@ module API
} if current_user_allowed_to(:add_work_package_watchers, context: represented.project) } if current_user_allowed_to(:add_work_package_watchers, context: represented.project)
end end
link :revisions do
{
href: api_v3_paths.work_package_revisions(represented.id)
} if current_user_allowed_to(:view_changesets, context: represented.project)
end
link :watch do link :watch do
{ {
href: api_v3_paths.work_package_watchers(represented.id), href: api_v3_paths.work_package_watchers(represented.id),

@ -82,6 +82,7 @@ module API
mount ::API::V3::Relations::RelationsAPI mount ::API::V3::Relations::RelationsAPI
mount ::API::V3::Activities::ActivitiesByWorkPackageAPI mount ::API::V3::Activities::ActivitiesByWorkPackageAPI
mount ::API::V3::Attachments::AttachmentsByWorkPackageAPI mount ::API::V3::Attachments::AttachmentsByWorkPackageAPI
mount ::API::V3::Repositories::RevisionsByWorkPackageAPI
mount ::API::V3::WorkPackages::UpdateFormAPI mount ::API::V3::WorkPackages::UpdateFormAPI
end end

@ -35,6 +35,10 @@ module OpenProject
class Base class Base
attr_accessor :url, :root_url attr_accessor :url, :root_url
def self.vendor
name.demodulize
end
def initialize(url, root_url = nil) def initialize(url, root_url = nil)
self.url = url self.url = url
self.root_url = root_url self.root_url = root_url
@ -57,7 +61,7 @@ module OpenProject
end end
def vendor def vendor
self.class.name.demodulize self.class.vendor
end end
def info def info

@ -44,7 +44,7 @@ module OpenProject
end end
def client_command def client_command
@client_command ||= config[:client_command] || 'git' @client_command ||= self.class.config[:client_command] || 'git'
end end
def client_version def client_version

@ -32,21 +32,26 @@ module OpenProject
module Scm module Scm
module Adapters module Adapters
module LocalClient module LocalClient
## def self.included(base)
# Determines local capabilities for SCM creation. base.extend(ClassMethods)
# Overridden by including classes when SCM may be remote.
def local?
true
end end
module ClassMethods
## ##
# Reads the configuration for this strategy from OpenProject's `configuration.yml`. # Reads the configuration for this strategy from OpenProject's `configuration.yml`.
def config def config
scm_config = OpenProject::Configuration ['scm', vendor].inject(OpenProject::Configuration) do |acc, key|
['scm', vendor].inject(scm_config) do |acc, key|
HashWithIndifferentAccess.new acc[key] HashWithIndifferentAccess.new acc[key]
end end
end end
end
##
# Determines local capabilities for SCM creation.
# Overridden by including classes when SCM may be remote.
def local?
true
end
## ##
# client executable command # client executable command

@ -36,11 +36,11 @@ module OpenProject
include LocalClient include LocalClient
def client_command def client_command
@client_command ||= config[:client_command] || 'svn' @client_command ||= self.class.config[:client_command] || 'svn'
end end
def svnadmin_command def svnadmin_command
@svnadmin_command ||= (config[:svnadmin_command] || 'svnadmin') @svnadmin_command ||= (self.class.config[:svnadmin_command] || 'svnadmin')
end end
def client_version def client_version

@ -30,6 +30,11 @@
module OpenProject module OpenProject
module Scm module Scm
module ManageableRepository module ManageableRepository
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
## ##
# We let SCM vendor implementation define their own # We let SCM vendor implementation define their own
# types (e.g., for differences in the management of # types (e.g., for differences in the management of
@ -37,25 +42,35 @@ module OpenProject
# #
# But if they are manageable by OpenProject, they must # But if they are manageable by OpenProject, they must
# expose this type through +available_types+. # expose this type through +available_types+.
MANAGED_TYPE = :managed def managed_type
:managed
end
## ##
# Reads from configuration whether new repositories of this kind # Reads from configuration whether new repositories of this kind
# may be managed from OpenProject. # may be managed from OpenProject.
def manageable? def manageable?
!managed_root.nil? ! (disabled_types.include?(managed_type) || managed_root.nil?)
end end
## ##
# Returns the managed root for this repository vendor # Returns the managed root for this repository vendor
def managed_root def managed_root
scm.config[:manages] scm_config[:manages]
end
end
##
#
def manageable?
self.class.manageable?
end end
## ##
# Determines whether this repository IS currently managed # Determines whether this repository IS currently managed
# by openproject # by openproject
def managed? def managed?
scm_type.to_sym == MANAGED_TYPE scm_type.to_sym == self.class.managed_type
end end
## ##
@ -73,7 +88,7 @@ module OpenProject
# Used only in the creation of a repository, at a later point # Used only in the creation of a repository, at a later point
# in time, it is referred to in the root_url # in time, it is referred to in the root_url
def managed_repository_path def managed_repository_path
File.join(managed_root, repository_path) File.join(self.class.managed_root, repository_path)
end end
## ##

@ -92,12 +92,11 @@ module Redmine
end end
end end
# Returns the mail adresses of users that should be notified # Returns users that should be notified
def recipients def recipients
notified = [] notified = []
notified = project.notified_users if project notified = project.notified_users if project
notified.reject! { |user| !visible?(user) } notified.select { |user| visible?(user) }
notified.map(&:mail)
end end
module ClassMethods module ClassMethods

@ -55,8 +55,7 @@ module Redmine::Acts::Journalized
def recipients def recipients
notified = [] notified = []
notified = project.notified_users if project notified = project.notified_users if project
notified.reject! { |user| !visible?(user) } notified.select { |user| visible?(user) }
notified.map(&:mail)
end end
def current_journal def current_journal

@ -66,10 +66,19 @@ module Redmine::Acts::Journalized
add_journal = journals.empty? || JournalManager.changed?(self) || !@journal_notes.empty? add_journal = journals.empty? || JournalManager.changed?(self) || !@journal_notes.empty?
JournalManager.add_journal self, @journal_user, @journal_notes if add_journal journal = JournalManager.add_journal self, @journal_user, @journal_notes if add_journal
journals.select(&:new_record?).each(&:save!) journals.select(&:new_record?).each(&:save!)
if add_journal
OpenProject::Notifications.send('journal_created',
journal: journal,
send_notification: JournalManager.send_notification)
end
# Need to clear the notification setting after each usage otherwise it might be cached
JournalManager.reset_notification
@journal_user = nil @journal_user = nil
@journal_notes = nil @journal_notes = nil
end end

@ -146,12 +146,10 @@ module Redmine
watcher_user_ids.any? { |uid| uid == user.id })) watcher_user_ids.any? { |uid| uid == user.id }))
end end
# Returns an array of watchers' email addresses # Returns an array of watchers
def watcher_recipients def watcher_recipients
notified = watcher_users.active.where(['mail_notification != ?', 'none']) notified = watcher_users.active.where(['mail_notification != ?', 'none'])
notified.select! { |user| possible_watcher?(user) } notified.select { |user| possible_watcher?(user) }
notified.map(&:mail).compact
end end
module ClassMethods; end module ClassMethods; end

@ -47,9 +47,7 @@ describe 'messages', type: :feature do
end end
before do before do
visit project_path(topic.board.project) visit topic_path(topic)
click_on 'Forums'
click_on topic.subject, match: :first
end end
describe 'clicking on quote', js: true do describe 'clicking on quote', js: true do

@ -140,6 +140,7 @@ describe 'Create repository', type: :feature, js: true do
find('input[type="radio"][value="managed"]').set(true) find('input[type="radio"][value="managed"]').set(true)
find('button[type="submit"]', text: I18n.t(:button_create)).click find('button[type="submit"]', text: I18n.t(:button_create)).click
expect(page).to have_selector('div.flash.notice')
expect(page).to have_selector('input[name="scm_type"][value="managed"]:checked') expect(page).to have_selector('input[name="scm_type"][value="managed"]:checked')
expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete)) expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete))
end end
@ -152,6 +153,7 @@ describe 'Create repository', type: :feature, js: true do
find('button[type="submit"]', text: I18n.t(:button_create)).click find('button[type="submit"]', text: I18n.t(:button_create)).click
expect(page).to have_selector('div.flash.notice')
expect(page).to have_selector('button[type="submit"]', text: I18n.t(:button_save)) expect(page).to have_selector('button[type="submit"]', text: I18n.t(:button_save))
expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete)) expect(page).to have_selector('a.icon-delete', text: I18n.t(:button_delete))
end end

@ -18,20 +18,26 @@ describe 'activity comments', js: true do
row = page.find("#work-package-#{work_package.id}") row = page.find("#work-package-#{work_package.id}")
row.double_click row.double_click
expect(find('#add-comment-text')).to be_present ng_wait
end end
it 'should alert user if navigating with unsaved form' do it 'should alert user if navigating with unsaved form' do
page.execute_script("jQuery('#add-comment-text').val('Foobar').trigger('change')") fill_in I18n.t('js.label_add_comment_title'), with: 'Foobar'
visit root_path visit root_path
page.driver.browser.switch_to.alert.accept page.driver.browser.switch_to.alert.accept
expect(current_path).to eq(root_path) expect(current_path).to eq(root_path)
end end
it 'should not alert if comment has been submitted' do it 'should not alert if comment has been submitted' do
page.execute_script("jQuery('#add-comment-text').val('Foobar').trigger('change')") fill_in I18n.t('js.label_add_comment_title'), with: 'Foobar'
page.execute_script("jQuery('#add-comment-text').siblings('button').trigger('click')")
click_button I18n.t('js.label_add_comment')
visit root_path visit root_path
expect(current_path).to eq(root_path) expect(current_path).to eq(root_path)
end end
end end

@ -87,6 +87,9 @@ class WorkPackagesPage
def ensure_index_page_loaded def ensure_index_page_loaded
if Capybara.current_driver == Capybara.javascript_driver if Capybara.current_driver == Capybara.javascript_driver
extend ::Angular::DSL unless singleton_class.included_modules.include?(::Angular::DSL)
ng_wait
expect(page).to have_selector('.advanced-filters--filter', visible: false) expect(page).to have_selector('.advanced-filters--filter', visible: false)
end end
end end

@ -81,13 +81,13 @@ describe Comment, type: :model do
news = FactoryGirl.create(:news, project: project, author: user) news = FactoryGirl.create(:news, project: project, author: user)
# with notifications for that event turned on # with notifications for that event turned on
allow(Notifier).to receive(:notify?).with(:news_comment_added).and_return(true) allow(Setting).to receive(:notified_events).and_return(['news_comment_added'])
assert_difference 'ActionMailer::Base.deliveries.size', 1 do assert_difference 'ActionMailer::Base.deliveries.size', 1 do
Comment.create!(commented: news, author: user, comments: 'more useful stuff') Comment.create!(commented: news, author: user, comments: 'more useful stuff')
end end
# with notifications for that event turned off # with notifications for that event turned off
allow(Notifier).to receive(:notify?).with(:news_comment_added).and_return(false) allow(Setting).to receive(:notified_events).and_return([])
assert_no_difference 'ActionMailer::Base.deliveries.size' do assert_no_difference 'ActionMailer::Base.deliveries.size' do
Comment.create!(commented: news, author: user, comments: 'more useful stuff') Comment.create!(commented: news, author: user, comments: 'more useful stuff')
end end

@ -1,133 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'legacy_spec_helper'
describe JournalObserver, type: :model do
before do
@type = FactoryGirl.create :type_with_workflow
@project = FactoryGirl.create :project,
types: [@type]
@workflow = @type.workflows.first
@user = FactoryGirl.create :user,
mail_notification: 'all',
member_in_project: @project
@issue = FactoryGirl.create :work_package,
project: @project,
author: @user,
type: @type,
status: @workflow.old_status
@user.members.first.roles << @workflow.role
@user.reload
allow(User).to receive(:current).and_return(@user)
ActionMailer::Base.deliveries.clear
end
context "#after_create for 'work_package_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['work_package_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.subject = 'A change to the issue'
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.subject = 'A change to the issue'
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'work_package_note_added'" do
it 'should send a notification when configured as a notification' do
@issue.recreate_initial_journal!
Setting.notified_events = ['work_package_note_added']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user, 'This update has a note')
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user, 'This update has a note')
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'status_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['status_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.status = @workflow.new_status
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.status = @workflow.new_status
assert @issue.save(validate: false)
end
end
end
context "#after_create for 'work_package_priority_updated'" do
it 'should send a notification when configured as a notification' do
Setting.notified_events = ['work_package_priority_updated']
assert_difference('ActionMailer::Base.deliveries.size', +1) do
@issue.add_journal(@user)
@issue.priority = IssuePriority.generate!
assert @issue.save(validate: false)
end
end
it 'should not send a notification with not configured' do
Setting.notified_events = []
assert_no_difference('ActionMailer::Base.deliveries.size') do
@issue.add_journal(@user)
@issue.priority = IssuePriority.generate!
assert @issue.save(validate: false)
end
end
end
end

@ -56,7 +56,7 @@ describe Journal, type: :model do
issue = WorkPackage.find(:first) issue = WorkPackage.find(:first)
user = User.find(:first) user = User.find(:first)
journal = issue.add_journal(user, 'A note') journal = issue.add_journal(user, 'A note')
JournalObserver.instance.send_notification = false JournalManager.send_notification = false
assert_difference('Journal.count') do assert_difference('Journal.count') do
assert issue.save assert issue.save

@ -364,7 +364,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = [] @jsmith.notified_project_ids = []
@jsmith.save @jsmith.save
@jsmith.reload @jsmith.reload
assert @jsmith.projects.first.recipients.include?(@jsmith.mail) assert @jsmith.projects.first.recipients.include?(@jsmith)
end end
it 'should mail notification selected' do it 'should mail notification selected' do
@ -372,7 +372,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = [1] @jsmith.notified_project_ids = [1]
@jsmith.save @jsmith.save
@jsmith.reload @jsmith.reload
assert Project.find(1).recipients.include?(@jsmith.mail) assert Project.find(1).recipients.include?(@jsmith)
end end
it 'should mail notification only my events' do it 'should mail notification only my events' do
@ -380,7 +380,7 @@ describe User, type: :model do
@jsmith.notified_project_ids = [] @jsmith.notified_project_ids = []
@jsmith.save @jsmith.save
@jsmith.reload @jsmith.reload
assert !@jsmith.projects.first.recipients.include?(@jsmith.mail) assert !@jsmith.projects.first.recipients.include?(@jsmith)
end end
it 'should comments sorting preference' do it 'should comments sorting preference' do

@ -92,10 +92,10 @@ describe Watcher do
assert @issue.watcher_recipients.empty? assert @issue.watcher_recipients.empty?
assert @issue.add_watcher(@user) assert @issue.add_watcher(@user)
assert_contains @issue.watcher_recipients, @user.mail assert_contains @issue.watcher_recipients, @user
@user.update_attribute :mail_notification, 'none' @user.update_attribute :mail_notification, 'none'
assert_does_not_contain @issue.watcher_recipients, @user.mail assert_does_not_contain @issue.watcher_recipients, @user
end end
it 'should unwatch' do it 'should unwatch' do

@ -0,0 +1,127 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::Repositories::RevisionRepresenter do
include ::API::V3::Utilities::PathHelper
let(:representer) { described_class.new(revision) }
let(:project) { FactoryGirl.build :project }
let(:repository) { FactoryGirl.build :repository_subversion, project: project }
let(:revision) {
FactoryGirl.build(:changeset,
id: 42,
revision: '1234',
repository: repository,
comments: commit_message,
committer: 'foo bar <foo@example.org>',
committed_on: DateTime.now,
)
}
let(:commit_message) { 'Some commit message' }
context 'generation' do
subject(:generated) { representer.to_json }
it { is_expected.to be_json_eql('Revision'.to_json).at_path('_type') }
describe 'revision' do
it { is_expected.to have_json_path('id') }
it_behaves_like 'API V3 formattable', 'message' do
let(:format) { 'plain' }
let(:raw) { revision.comments }
let(:html) { '<p>' + revision.comments + '</p>' }
end
describe 'identifier' do
it { is_expected.to have_json_path('identifier') }
it { is_expected.to be_json_eql('1234'.to_json).at_path('identifier') }
end
describe 'createdAt' do
it_behaves_like 'has UTC ISO 8601 date and time' do
let(:date) { revision.committed_on }
let(:json_path) { 'createdAt' }
end
end
describe 'authorName' do
it { is_expected.to have_json_path('authorName') }
it { is_expected.to be_json_eql('foo bar '.to_json).at_path('authorName') }
end
end
context 'with referencing commit message' do
let(:work_package) { FactoryGirl.build_stubbed(:work_package, project: project) }
let(:commit_message) { "Totally references ##{work_package.id}" }
let(:html_reference) {
id = work_package.id
str = "Totally references <a href=\"/work_packages/#{id}\""
str << " class=\"issue work_package status-1 priority-1 parent\""
str << " title=\"#{work_package.subject} (#{work_package.status})\">"
str << "##{id}</a>"
}
before do
allow(WorkPackage).to receive(:find_by_id).and_return(work_package)
end
it_behaves_like 'API V3 formattable', 'message' do
let(:format) { 'plain' }
let(:raw) { revision.comments }
let(:html) { '<p>' + html_reference + '</p>' }
end
end
describe 'author' do
context 'with no linked user' do
it_behaves_like 'has no link' do
let(:link) { 'author' }
end
end
context 'with linked user as author' do
let(:user) { FactoryGirl.build(:user) }
before do
allow(revision).to receive(:user).and_return(user)
end
it_behaves_like 'has a titled link' do
let(:link) { 'author' }
let(:href) { api_v3_paths.user(user.id) }
let(:title) { user.name }
end
end
end
end
end

@ -239,6 +239,16 @@ describe ::API::V3::Utilities::PathHelper do
end end
end end
describe 'revisions paths' do
describe '#revision' do
subject { helper.revision 1 }
it_behaves_like 'api v3 path'
it { is_expected.to eql('/api/v3/revisions/1') }
end
end
describe 'schemas paths' do describe 'schemas paths' do
describe '#work_package_schema' do describe '#work_package_schema' do
subject { helper.work_package_schema 1, 2 } subject { helper.work_package_schema 1, 2 }
@ -399,6 +409,14 @@ describe ::API::V3::Utilities::PathHelper do
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/relations\/1/) } it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/relations\/1/) }
end end
describe '#work_package_revisions' do
subject { helper.work_package_revisions 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to eql('/api/v3/work_packages/42/revisions') }
end
describe '#work_package_form' do describe '#work_package_form' do
subject { helper.work_package_form 1 } subject { helper.work_package_form 1 }

@ -325,6 +325,26 @@ describe ::API::V3::WorkPackages::WorkPackageRepresenter do
end end
end end
describe 'revisions' do
context 'when the user lacks the view_changesets permission' do
it_behaves_like 'has no link' do
let(:link) { 'revisions' }
end
end
context 'when the user has the required permission' do
let(:revision_permissions) { [:view_changesets] }
let(:role) { FactoryGirl.create :role, permissions: permissions + revision_permissions }
it_behaves_like 'has an untitled link' do
let(:link) { 'revisions' }
let(:href) {
api_v3_paths.work_package_revisions(work_package.id)
}
end
end
end
describe 'version' do describe 'version' do
let(:embedded_path) { '_embedded/version' } let(:embedded_path) { '_embedded/version' }
let(:href_path) { '_links/version/href' } let(:href_path) { '_links/version/href' }

@ -44,7 +44,7 @@ describe OpenProject::Scm::Adapters::Git do
} }
before do before do
allow(adapter).to receive(:config).and_return(config) allow(adapter.class).to receive(:config).and_return(config)
end end
describe 'client information' do describe 'client information' do

@ -36,7 +36,7 @@ describe OpenProject::Scm::Adapters::Subversion do
let(:adapter) { OpenProject::Scm::Adapters::Subversion.new url, root_url } let(:adapter) { OpenProject::Scm::Adapters::Subversion.new url, root_url }
before do before do
allow(adapter).to receive(:config).and_return(config) allow(adapter.class).to receive(:config).and_return(config)
end end
describe 'client information' do describe 'client information' do

@ -0,0 +1,156 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe JournalNotificationMailer do
let(:project) { FactoryGirl.create(:project_with_types) }
let(:user) do
FactoryGirl.build(:user,
mail_notification: 'all',
member_in_project: project)
end
let(:work_package) {
FactoryGirl.create(:work_package,
project: project,
author: user,
type: project.types.first)
}
let(:notifications) { [] }
before do
allow(User).to receive(:current).and_return(user)
allow(Setting).to receive(:notified_events).and_return(notifications)
ActionMailer::Base.deliveries.clear
end
shared_examples_for 'handles deliveries' do |notification_setting|
context 'setting enabled' do
let(:notifications) { [notification_setting] }
it 'sends a notification' do
expect(ActionMailer::Base.deliveries.size).to eq(1)
end
context 'insufficient work package changes' do
let!(:another_work_package) {
FactoryGirl.create(:work_package,
project: project,
author: user,
type: project.types.first)
}
before do
ActionMailer::Base.deliveries.clear
another_work_package.add_journal(user)
another_work_package.description = 'needs more changes'
another_work_package.save!(validate: false)
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
describe 'journal creation' do
context 'work_package_created' do
before do
FactoryGirl.create(:work_package, project: project)
end
it_behaves_like 'handles deliveries', 'work_package_added'
end
context 'work_package_updated' do
before do
work_package.add_journal(user)
work_package.subject = 'A change to the issue'
work_package.save!(validate: false)
end
context 'setting enabled' do
let(:notifications) { ['work_package_updated'] }
it 'sends a notification' do
expect(ActionMailer::Base.deliveries.size).to eq(1)
end
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
context 'work_package_note_added' do
before do
work_package.add_journal(user, 'This update has a note')
work_package.save!(validate: false)
work_package.recreate_initial_journal!
end
it_behaves_like 'handles deliveries', 'work_package_note_added'
end
context 'status_updated' do
before do
work_package.add_journal(user)
work_package.status = FactoryGirl.build(:status)
work_package.save!(validate: false)
end
it_behaves_like 'handles deliveries', 'status_updated'
end
context 'work_package_priority_updated' do
before do
work_package.add_journal(user)
work_package.priority = IssuePriority.generate!
work_package.save!(validate: false)
end
it_behaves_like 'handles deliveries', 'work_package_priority_updated'
end
context 'send_notification disabled' do
before do
allow(JournalManager).to receive(:send_notification).and_return(false)
FactoryGirl.create(:work_package, project: project) # Provoke notification
end
it 'sends no notification' do
expect(ActionMailer::Base.deliveries.size).to eq(0)
end
end
end
end

@ -37,7 +37,21 @@ describe Repository::Git, type: :model do
before do before do
allow(Setting).to receive(:enabled_scm).and_return(['Git']) allow(Setting).to receive(:enabled_scm).and_return(['Git'])
allow(instance).to receive(:scm).and_return(adapter) allow(instance).to receive(:scm).and_return(adapter)
allow(adapter).to receive(:config).and_return(config) allow(adapter.class).to receive(:config).and_return(config)
end
describe 'available types' do
it 'allow local by default' do
expect(instance.class.available_types).to eq([:local])
end
context 'with disabled typed' do
let(:config) { { disabled_types: [:local, :managed] } }
it 'does not have any types' do
expect(instance.class.available_types).to be_empty
end
end
end end
describe 'managed git' do describe 'managed git' do
@ -53,6 +67,16 @@ describe Repository::Git, type: :model do
it 'is manageable' do it 'is manageable' do
expect(instance.manageable?).to be true expect(instance.manageable?).to be true
expect(instance.class.available_types).to eq([:local, :managed])
end
context 'with disabled managed typed' do
let(:config) { { disabled_types: [:managed] } }
it 'is no longer manageable' do
expect(instance.class.available_types).to eq([:local])
expect(instance.manageable?).to be false
end
end end
context 'and associated project' do context 'and associated project' do

@ -36,7 +36,7 @@ describe Repository::Subversion, type: :model do
before do before do
allow(Setting).to receive(:enabled_scm).and_return(['Subversion']) allow(Setting).to receive(:enabled_scm).and_return(['Subversion'])
allow(instance).to receive(:scm).and_return(adapter) allow(instance).to receive(:scm).and_return(adapter)
allow(adapter).to receive(:config).and_return(config) allow(instance.class).to receive(:scm_config).and_return(config)
end end
describe 'default Subversion' do describe 'default Subversion' do
@ -45,7 +45,15 @@ describe Repository::Subversion, type: :model do
end end
it 'has one available type' do it 'has one available type' do
expect(instance.supported_types).to eq [:existing] expect(instance.class.available_types).to eq [:existing]
end
context 'with disabled typed' do
let(:config) { { disabled_types: [:existing, :managed] } }
it 'does not have any types' do
expect(instance.class.available_types).to be_empty
end
end end
end end
@ -62,10 +70,16 @@ describe Repository::Subversion, type: :model do
it 'is manageable' do it 'is manageable' do
expect(instance.manageable?).to be true expect(instance.manageable?).to be true
expect(instance.class.available_types).to eq([:existing, :managed])
end end
it 'has two available types' do context 'with disabled managed typed' do
expect(instance.supported_types).to eq [:existing, :managed] let(:config) { { disabled_types: [:managed] } }
it 'is no longer manageable' do
expect(instance.class.available_types).to eq([:existing])
expect(instance.manageable?).to be false
end
end end
context 'and associated project' do context 'and associated project' do

@ -31,20 +31,23 @@ require 'spec_helper'
describe WorkPackage, type: :model do describe WorkPackage, type: :model do
describe ActionMailer::Base do describe ActionMailer::Base do
let(:user_1) { let(:user_1) {
FactoryGirl.create(:user, FactoryGirl.build(:user,
mail: 'dlopper@somenet.foo') mail: 'dlopper@somenet.foo',
member_in_project: project)
} }
let(:user_2) { let(:user_2) {
FactoryGirl.create(:user, FactoryGirl.build(:user,
mail: 'jsmith@somenet.foo') mail: 'jsmith@somenet.foo',
member_in_project: project)
} }
let(:work_package) { FactoryGirl.build(:work_package) } let(:project) { FactoryGirl.create(:project) }
let(:work_package) { FactoryGirl.build(:work_package, project: project) }
before do before do
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
allow(work_package).to receive(:recipients).and_return([user_1.mail]) allow(work_package).to receive(:recipients).and_return([user_1])
allow(work_package).to receive(:watcher_recipients).and_return([user_2.mail]) allow(work_package).to receive(:watcher_recipients).and_return([user_2])
work_package.save work_package.save
end end
@ -73,7 +76,7 @@ describe WorkPackage, type: :model do
before do before do
ActionMailer::Base.deliveries.clear ActionMailer::Base.deliveries.clear
WorkPackageObserver.instance.send_notification = false JournalManager.send_notification = false
work_package.save! work_package.save!
end end
@ -91,7 +94,7 @@ describe WorkPackage, type: :model do
subject { work_package.recipients } subject { work_package.recipients }
it { is_expected.to include(user_1.mail) } it { is_expected.to include(user_1) }
end end
end end
end end

@ -191,12 +191,12 @@ describe WorkPackage, type: :model do
context 'pre-condition' do context 'pre-condition' do
subject { work_package.recipients } subject { work_package.recipients }
it { is_expected.to include(work_package.author.mail) } it { is_expected.to include(work_package.author) }
end end
subject { copy.recipients } subject { copy.recipients }
it { is_expected.not_to include(copy.author.mail) } it { is_expected.not_to include(copy.author) }
end end
describe 'with children' do describe 'with children' do

@ -1037,7 +1037,7 @@ describe WorkPackage, type: :model do
it { is_expected.not_to be_nil } it { is_expected.not_to be_nil }
end end
let(:expected_users) { work_package.author.mail } let(:expected_users) { work_package.author }
it_behaves_like 'includes expected users' it_behaves_like 'includes expected users'
end end
@ -1051,7 +1051,7 @@ describe WorkPackage, type: :model do
it { is_expected.not_to be_nil } it { is_expected.not_to be_nil }
end end
let(:expected_users) { work_package.assigned_to.mail } let(:expected_users) { work_package.assigned_to }
it_behaves_like 'includes expected users' it_behaves_like 'includes expected users'
end end

@ -0,0 +1,98 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Revisions by work package resource', type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) {
FactoryGirl.create(:user,
member_in_project: project,
member_through_role: role)
}
let(:project) { FactoryGirl.create(:project, is_public: false) }
let(:role) { FactoryGirl.create(:role, permissions: permissions) }
let(:permissions) { [:view_work_packages, :view_changesets] }
let(:repository) { FactoryGirl.create(:repository_subversion, project: project) }
let(:work_package) { FactoryGirl.create(:work_package, author: current_user, project: project) }
let(:revisions) { [] }
subject(:response) { last_response }
before do
allow(User).to receive(:current).and_return current_user
end
describe '#get' do
let(:get_path) { api_v3_paths.work_package_revisions work_package.id }
before do
revisions.each do |rev| rev.save! end
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 0, 0, 'Revision'
context 'with existing revisions' do
let(:revisions) {
FactoryGirl.build_list(:changeset,
5,
comments: "This commit references ##{work_package.id}",
repository: repository
)
}
it_behaves_like 'API V3 collection response', 5, 5, 'Revision'
end
context 'user unauthorized to view work package' do
let(:current_user) { FactoryGirl.create(:user) }
it 'should respond with 404' do
expect(subject.status).to eq(404)
end
end
context 'user unauthorized to view revisions' do
let(:permissions) { [:view_work_packages] }
it 'should respond with 403' do
expect(subject.status).to eq(403)
end
end
end
end

@ -0,0 +1,97 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Revisions resource', type: :request do
include Rack::Test::Methods
include Capybara::RSpecMatchers
include API::V3::Utilities::PathHelper
let(:revision) {
FactoryGirl.create(:changeset,
repository: repository,
comments: 'Some commit message',
committer: 'foo bar <foo@example.org>'
)
}
let(:repository) {
FactoryGirl.create(:repository_subversion, project: project)
}
let(:project) {
FactoryGirl.create(:project, identifier: 'test_project', is_public: false)
}
let(:role) {
FactoryGirl.create(:role,
permissions: [:view_changesets])
}
let(:current_user) {
FactoryGirl.create(:user, member_in_project: project, member_through_role: role)
}
let(:unauthorized_user) { FactoryGirl.create(:user) }
describe '#get' do
let(:get_path) { api_v3_paths.revision revision.id }
context 'when acting as a user with permission to view revisions' do
before(:each) do
allow(User).to receive(:current).and_return current_user
get get_path
end
it 'should respond with 200' do
expect(last_response.status).to eq(200)
end
describe 'response body' do
subject(:response) { last_response.body }
it 'should respond with revision in HAL+JSON format' do
is_expected.to be_json_eql(revision.id.to_json).at_path('id')
end
end
context 'requesting nonexistent revision' do
let(:get_path) { api_v3_paths.revision 909090 }
it_behaves_like 'not found'
end
end
context 'when acting as an user without permission to view work package' do
before(:each) do
allow(User).to receive(:current).and_return unauthorized_user
get get_path
end
it_behaves_like 'not found'
end
end
end

@ -133,7 +133,7 @@ MESSAGE
subject { model_instance.watcher_recipients } subject { model_instance.watcher_recipients }
it { is_expected.to match_array([watching_user.mail]) } it { is_expected.to match_array([watching_user]) }
context 'when the permission to watch has been removed' do context 'when the permission to watch has been removed' do
before do before do

Loading…
Cancel
Save