attachments for meeting contents (MeetingAgenda, MeetingMinutes)

This commit is contained in:
Markus Kahl
2019-05-21 13:27:35 +01:00
committed by Oliver Günther
parent 4fda229361
commit bc5adfcba7
24 changed files with 824 additions and 500 deletions
+15
View File
@@ -175,6 +175,21 @@ class Attachment < ActiveRecord::Base
content_type || fallback
end
def copy(&block)
attachment = dup
attachment.file = diskfile
yield attachment if block_given?
attachment
end
def copy!(&block)
attachment = copy &block
attachment.save!
end
def extract_fulltext
return unless OpenProject::Database.allows_tsv?
job = ExtractFulltextJob.new(id)
@@ -0,0 +1,46 @@
//-- 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.
//++
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {Attachable} from 'core-app/modules/hal/resources/mixins/attachable-mixin';
export interface MeetingContentResourceLinks {
addAttachment(attachment:HalResource):Promise<any>;
}
class MeetingContentBaseResource extends HalResource {
public $links:MeetingContentResourceLinks;
private attachmentsBackend = false;
}
export const MeetingContentResource = Attachable(MeetingContentBaseResource);
export interface MeetingContentResource extends HalResource {
}
@@ -51,6 +51,7 @@ import {
import {Injectable} from '@angular/core';
import {HalResource} from 'core-app/modules/hal/resources/hal-resource';
import {WikiPageResource} from "core-app/modules/hal/resources/wiki-page-resource";
import {MeetingContentResource} from "core-app/modules/hal/resources/meeting-content-resource";
import {PostResource} from "core-app/modules/hal/resources/post-resource";
import {StatusResource} from "core-app/modules/hal/resources/status-resource";
import {GridWidgetResource} from "core-app/modules/hal/resources/grid-widget-resource";
@@ -158,6 +159,9 @@ const halResourceDefaultConfig:{ [typeName:string]:HalResourceFactoryConfigInter
WikiPage: {
cls: WikiPageResource
},
MeetingContent: {
cls: MeetingContentResource
},
Post: {
cls: PostResource
},
@@ -51,6 +51,7 @@ class MeetingContentsController < ApplicationController
(render_403; return) unless @content.editable? # TODO: not tested!
@content.attributes = content_params
@content.author = User.current
@content.attach_files(permitted_params.attachments.to_h)
if @content.save
flash[:notice] = l(:notice_successful_update)
redirect_back_or_default controller: '/meetings', action: 'show', id: @meeting
+16 -2
View File
@@ -181,8 +181,22 @@ class Meeting < ActiveRecord::Base
end
def close_agenda_and_copy_to_minutes!
agenda.lock!
create_minutes(text: agenda.text, comment: 'Minutes created')
Meeting.transaction do
agenda.lock!
attachments = agenda.attachments.map { |a| [a, a.copy] }
minutes = create_minutes(text: agenda.text, comment: 'Minutes created', attachments: attachments.map(&:last))
# substitute attachment references in text to use the respective copied attachments
text = agenda.text.gsub(/(?<=\(\/api\/v3\/attachments\/)\d+(?=\/content\))/) do |id|
old_id = id.to_i
new_id = attachments.select { |a, _| a.id == old_id }.map { |_, a| a.id }.first
new_id || -1
end
minutes.update text: text
end
end
alias :original_participants_attributes= :participants_attributes=
@@ -25,6 +25,8 @@ require_dependency 'wiki_page'
require 'wiki_page'
class MeetingContent < ActiveRecord::Base
include OpenProject::Journal::AttachmentHelper
belongs_to :meeting
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
@@ -34,6 +36,16 @@ class MeetingContent < ActiveRecord::Base
before_save :comment_to_journal_notes
acts_as_attachable(
after_remove: :attachments_changed,
order: "#{Attachment.table_name}.file",
add_on_new_permission: :create_meetings,
add_on_persisted_permission: :edit_meetings,
view_permission: :view_meetings,
delete_permission: :edit_meetings,
modification_blocked: ->(*) { false }
)
acts_as_journalized
acts_as_event type: Proc.new { |o| "#{o.class.to_s.underscore.dasherize}" },
title: Proc.new { |o| "#{o.class.model_name.human}: #{o.meeting.title}" },
@@ -19,20 +19,28 @@ See doc/COPYRIGHT.md for more details.
++#%>
<%= form_for content, :url => {:controller => '/' + content_type.pluralize, :action => 'update', :meeting_id => content.meeting}, :html => {:id => "#{content_type}_form", :method => :put} do |f| %>
<%= labelled_tabular_form_for content, :url => {:controller => '/' + content_type.pluralize, :action => 'update', :meeting_id => content.meeting}, :html => {:id => "#{content_type}_form", :method => :put} do |f| %>
<%= error_messages_for content_type %>
<% resource = ::API::V3::MeetingContents::MeetingContentRepresenter.new(content, current_user: current_user, embed_links: true) %>
<p>
<%= f.text_area :text,
class: 'wiki-edit' %>
<%=
f.text_area(
:text,
class: 'wiki-edit wiki-toolbar',
resource: resource,
label_options: { class: 'hidden-for-sighted' },
with_text_formatting: true
)
%>
</p>
<%= f.hidden_field :lock_version %>
<% path = send("preview_#{content_type}_path", content.meeting) %>
<%= wikitoolbar_for "#{content_type}_text", preview_context: preview_context(content.meeting, @project) %>
<p><label for="<%= content_type %>_comment"><%= Meeting.human_attribute_name(:comments) %></label><%= f.text_field :comment, :size => 120 %></p>
<p><%= f.text_field :comment, :size => 120 %></p>
<p><%= styled_button_tag l(:button_save), class: '-highlight -with-icon icon-checkmark button--save-agenda' %>
<%= link_to l(:button_cancel), "#", data: { 'content-type': content_type }, class: 'button button--cancel-agenda' %>
<%= link_to t(:button_cancel), "#", data: { 'content-type': content_type }, class: 'button button--cancel-agenda' %>
<% end %>
<%= render :partial => 'shared/meeting_header' %>
@@ -54,4 +54,7 @@ See doc/COPYRIGHT.md for more details.
<% end -%>
</div>
<% resource = ::API::V3::MeetingContents::MeetingContentRepresenter.new(content, current_user: current_user, embed_links: true) %>
<%= list_attachments(resource) %>
<%= render :partial => 'shared/meeting_header' %>
@@ -0,0 +1,52 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Attachments
class AttachmentsByMeetingContentAPI < ::API::OpenProjectAPI
resources :attachments do
helpers API::V3::Attachments::AttachmentsByContainerAPI::Helpers
helpers do
def container
meeting_content
end
def get_attachment_self_path
api_v3_paths.attachments_by_meeting_content container.id
end
end
get &API::V3::Attachments::AttachmentsByContainerAPI.read
post &API::V3::Attachments::AttachmentsByContainerAPI.create
end
end
end
end
end
@@ -0,0 +1,38 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module MeetingAgendas
class MeetingAgendaRepresenter < API::V3::MeetingContents::MeetingContentRepresenter
end
end
end
end
@@ -0,0 +1,58 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module MeetingContents
class MeetingContentRepresenter < ::API::Decorators::Single
include API::Decorators::LinkedResource
include API::Caching::CachedRepresenter
include ::API::V3::Attachments::AttachableRepresenterMixin
self_link title_getter: ->(*) { nil }
property :id
associated_resource :project,
link: ->(*) do
next unless represented.project.present?
{
href: api_v3_paths.project(represented.project.id),
title: represented.project.name
}
end
def _type
'MeetingContent'
end
end
end
end
end
@@ -0,0 +1,38 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module MeetingMinutes
class MeetingMinutesRepresenter < API::V3::MeetingContents::MeetingContentRepresenter
end
end
end
end
@@ -0,0 +1,53 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Meetings
class MeetingContentsAPI < ::API::OpenProjectAPI
resources :meeting_contents do
helpers do
def meeting_content
MeetingContent.find params[:id]
end
end
route_param :id do
get do
::API::V3::MeetingContents::MeetingContentRepresenter.new(
meeting_content, current_user: current_user, embed_links: true
)
end
mount ::API::V3::Attachments::AttachmentsByMeetingContentAPI
end
end
end
end
end
end
@@ -69,6 +69,10 @@ module OpenProject::Meeting
patch_with_namespace :OpenProject, :TextFormatting, :Formats, :Markdown, :TextileConverter
add_api_endpoint 'API::V3::Root' do
mount ::API::V3::Meetings::MeetingContentsAPI
end
initializer 'meeting.precompile_assets' do
Rails.application.config.assets.precompile += %w(meeting/meeting.css meeting/meeting.js)
end
@@ -91,5 +95,29 @@ module OpenProject::Meeting
PermittedParams.permit(:search, :meetings)
end
add_api_path :meeting_content do |id|
"#{root}/meeting_contents/#{id}"
end
add_api_path :meeting_agenda do |id|
meeting_content(id)
end
add_api_path :meeting_minutes do |id|
meeting_content(id)
end
add_api_path :attachments_by_meeting_content do |id|
"#{meeting_content(id)}/attachments"
end
add_api_path :attachments_by_meeting_agenda do |id|
attachments_by_meeting_content id
end
add_api_path :attachments_by_meeting_minutes do |id|
attachments_by_meeting_content id
end
end
end
@@ -0,0 +1,78 @@
require 'spec_helper'
require 'features/page_objects/notification'
describe 'Add an attachment to a meeting (agenda)', js: true do
let(:role) do
FactoryBot.create :role, permissions: %i[view_meetings edit_meetings create_meeting_agendas]
end
let(:dev) do
FactoryBot.create :user, member_in_project: project, member_through_role: role
end
let(:project) { FactoryBot.create(:project) }
let(:meeting) do
FactoryBot.create(
:meeting,
project: project,
title: "Versammlung",
agenda: FactoryBot.create(:meeting_agenda, text: "Versammlung")
)
end
let(:attachments) { ::Components::Attachments.new }
let(:image_fixture) { Rails.root.join('spec/fixtures/files/image.png') }
let(:editor) { Components::WysiwygEditor.new }
before do
login_as(dev)
visit "/meetings/#{meeting.id}"
within "#tab-content-agenda .toolbar" do
click_button "Edit"
end
end
describe 'wysiwyg editor' do
context 'on an existing page' do
it 'can upload an image via drag & drop' do
target = find('.ck-content')
editor.expect_button 'Insert image'
editor.drag_attachment image_fixture, 'Some image caption'
click_on "Save"
content = find("div.meeting_content.meeting_agenda")
expect(content).to have_selector('img')
expect(content).to have_content('Some image caption')
end
end
end
describe 'attachment dropzone' do
it 'can upload an image via attaching and drag & drop' do
# called the same for all Wysiwyg dditors no matter if for work packages
# or not
container = page.find('.wp-attachment-upload')
scroll_to_element(container)
##
# Attach file manually
expect(page).to have_no_selector('.work-package--attachments--filename')
attachments.attach_file_on_input(image_fixture)
expect(page).not_to have_selector('notification-upload-progress')
expect(page).to have_selector('.work-package--attachments--filename', text: 'image.png', wait: 5)
##
# and via drag & drop
attachments.drag_and_drop_file(container, Rails.root.join('spec/fixtures/files/image.png'))
expect(page).not_to have_selector('notification-upload-progress')
expect(page).to have_selector('.work-package--attachments--filename', text: 'image.png', count: 2, wait: 5)
end
end
end
@@ -0,0 +1,43 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'requests/api/v3/attachments/attachment_resource_shared_examples'
describe "meeting agenda attachments" do
it_behaves_like "an APIv3 attachment resource" do
let(:attachment_type) { :meeting_content }
let(:create_permission) { :create_meetings }
let(:read_permission) { :view_meetings }
let(:update_permission) { :edit_meetings }
let(:meeting_content) { FactoryBot.create :meeting_agenda, meeting: meeting }
let(:meeting) { FactoryBot.create :meeting, project: project }
end
end
@@ -0,0 +1,43 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'requests/api/v3/attachments/attachment_resource_shared_examples'
describe "meeting minutes attachments" do
it_behaves_like "an APIv3 attachment resource" do
let(:attachment_type) { :meeting_content }
let(:create_permission) { :create_meetings }
let(:read_permission) { :view_meetings }
let(:update_permission) { :edit_meetings }
let(:meeting_content) { FactoryBot.create :meeting_minutes, meeting: meeting }
let(:meeting) { FactoryBot.create :meeting, project: project }
end
end
@@ -29,30 +29,37 @@
require 'spec_helper'
require 'rack/test'
describe 'API v3 Attachment resource', type: :request, content_type: :json do
shared_examples 'an APIv3 attachment resource', type: :request, content_type: :json do |include_by_container = true|
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) do
let(:current_user) { user_with_permissions }
let(:user_with_permissions) do
FactoryBot.create(:user, member_in_project: project, member_through_role: role)
end
let(:author) do
current_user
end
let(:project) { FactoryBot.create(:project, is_public: false) }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) do
%i[view_work_packages view_wiki_pages delete_wiki_pages_attachments
edit_work_packages edit_wiki_pages edit_messages]
end
let(:work_package) { FactoryBot.create(:work_package, author: current_user, project: project) }
let(:attachment) { FactoryBot.create(:attachment, container: container, author: author) }
let(:wiki) { FactoryBot.create(:wiki, project: project) }
let(:wiki_page) { FactoryBot.create(:wiki_page, wiki: wiki) }
let(:forum) { FactoryBot.create(:forum, project: project) }
let(:forum_message) { FactoryBot.create(:message, forum: forum) }
let(:container) { work_package }
let(:container) { send attachment_type }
let(:attachment_type) { raise "attachment type goes here, e.g. work_package" }
let(:permissions) { all_permissions }
let(:all_permissions) { Array([create_permission, read_permission, update_permission]).flatten.compact }
let(:create_permission) { raise "permissions go here, e.g. add_work_packages" }
let(:read_permission) { raise "permissions go here, e.g. view_work_packages" }
let(:update_permission) { raise "permissions go here, e.g. edit_work_packages" }
let(:missing_permissions_user) { user_with_permissions }
before do
allow(User).to receive(:current).and_return current_user
@@ -62,43 +69,36 @@ describe 'API v3 Attachment resource', type: :request, content_type: :json do
subject(:response) { last_response }
let(:get_path) { api_v3_paths.attachment attachment.id }
%i[wiki_page work_package forum_message].each do |attachment_type|
context "with a #{attachment_type} attachment" do
let(:container) { send(attachment_type) }
let(:container) { send(attachment_type) }
context 'logged in user' do
before do
get get_path
end
context 'logged in user' do
before do
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it 'should respond with correct attachment' do
expect(subject.body).to be_json_eql(attachment.filename.to_json).at_path('fileName')
end
it 'should respond with correct attachment' do
expect(subject.body).to be_json_eql(attachment.filename.to_json).at_path('fileName')
end
context 'requesting nonexistent attachment' do
let(:get_path) { api_v3_paths.attachment 9999 }
context 'requesting nonexistent attachment' do
let(:get_path) { api_v3_paths.attachment 9999 }
it_behaves_like 'not found' do
let(:id) { 9999 }
let(:type) { 'Attachment' }
end
end
it_behaves_like 'not found' do
let(:id) { 9999 }
let(:type) { 'Attachment' }
end
end
context 'requesting attachments without sufficient permissions' do
if attachment_type == :forum_message
let(:current_user) { FactoryBot.create(:user) }
else
let(:permissions) { [] }
end
context 'requesting attachments without sufficient permissions' do
let(:current_user) { missing_permissions_user }
let(:permissions) { all_permissions - Array(read_permission) }
it_behaves_like 'not found' do
let(:type) { 'Attachment' }
end
end
it_behaves_like 'not found' do
let(:type) { 'Attachment' }
end
end
end
@@ -208,31 +208,25 @@ describe 'API v3 Attachment resource', type: :request, content_type: :json do
end
end
%i[wiki_page work_package forum_message].each do |attachment_type|
context "with a #{attachment_type} attachment" do
let(:container) { send(attachment_type) }
context 'with required permissions' do
it_behaves_like 'deletes the attachment'
context 'with required permissions' do
it_behaves_like 'deletes the attachment'
context 'for a non-existent attachment' do
let(:path) { api_v3_paths.attachment 1337 }
context 'for a non-existent attachment' do
let(:path) { api_v3_paths.attachment 1337 }
it_behaves_like 'not found' do
let(:id) { 1337 }
let(:type) { 'Attachment' }
end
end
end
context 'without required permissions' do
let(:permissions) { %i[view_work_packages view_wiki_pages] }
it_behaves_like 'does not delete the attachment'
it_behaves_like 'not found' do
let(:id) { 1337 }
let(:type) { 'Attachment' }
end
end
end
context 'without required permissions' do
let(:permissions) { all_permissions - Array(update_permission) }
it_behaves_like 'does not delete the attachment'
end
context "with an uncontainered attachment" do
let(:container) { nil }
@@ -308,4 +302,99 @@ describe 'API v3 Attachment resource', type: :request, content_type: :json do
end
end
end
context 'by container', if: include_by_container do
subject(:response) { last_response }
describe '#get' do
let(:get_path) { api_v3_paths.send "attachments_by_#{attachment_type}", container.id }
before do
FactoryBot.create_list(:attachment, 2, container: container)
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 2, 2, 'Attachment'
end
describe '#post' do
let(:request_path) { api_v3_paths.send "attachments_by_#{attachment_type}", container.id }
let(:request_parts) { { metadata: metadata, file: file } }
let(:metadata) { { fileName: 'cat.png' }.to_json }
let(:file) { mock_uploaded_file(name: 'original-filename.txt') }
let(:max_file_size) { 1 } # given in kiB
before do
allow(Setting).to receive(:attachment_max_size).and_return max_file_size.to_s
post request_path, request_parts
end
it 'should respond with HTTP Created' do
expect(subject.status).to eq(201)
end
it 'should return the new attachment' do
expect(subject.body).to be_json_eql('Attachment'.to_json).at_path('_type')
end
it 'ignores the original file name' do
expect(subject.body).to be_json_eql('cat.png'.to_json).at_path('fileName')
end
context 'metadata section is missing' do
let(:request_parts) { { file: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'file section is missing' do
# rack-test won't send a multipart request without a file being present
# however as long as we depend on correctly named sections this test should do just fine
let(:request_parts) { { metadata: metadata, wrongFileSection: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'metadata section is no valid JSON' do
let(:metadata) { '"fileName": "cat.png"' }
it_behaves_like 'parse error'
end
context 'metadata is missing the fileName' do
let(:metadata) { Hash.new.to_json }
it_behaves_like 'constraint violation' do
let(:message) { "fileName #{I18n.t('activerecord.errors.messages.blank')}" }
end
end
context 'file is too large' do
let(:file) { mock_uploaded_file(content: 'a' * 2.kilobytes) }
let(:expanded_localization) do
I18n.t('activerecord.errors.messages.file_too_large', count: max_file_size.kilobytes)
end
it_behaves_like 'constraint violation' do
let(:message) { "File #{expanded_localization}" }
end
end
context 'only allowed to add, but not to edit' do
let(:permissions) { all_permissions - Array(update_permission) }
it_behaves_like 'unauthorized access'
end
context 'only allowed to view' do
let(:permissions) { Array(read_permission) }
it_behaves_like 'unauthorized access'
end
end
end
end
@@ -1,146 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Attachments by post resource', type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) do
FactoryBot.create(:user,
member_in_project: project,
member_through_role: role)
end
let(:project) { FactoryBot.create(:project) }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) { [:view_messages] }
let(:forum) { FactoryBot.create(:forum, project: project) }
let(:forum_message) { FactoryBot.create(:message, forum: forum) }
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.attachments_by_post forum_message.id }
before do
FactoryBot.create_list(:attachment, 2, container: forum_message)
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 2, 2, 'Attachment'
end
describe '#post' do
let(:permissions) { %i[view_messages edit_messages] }
let(:request_path) { api_v3_paths.attachments_by_post forum_message.id }
let(:request_parts) { { metadata: metadata, file: file } }
let(:metadata) { { fileName: 'cat.png' }.to_json }
let(:file) { mock_uploaded_file(name: 'original-filename.txt') }
let(:max_file_size) { 1 } # given in kiB
before do
allow(Setting).to receive(:attachment_max_size).and_return max_file_size.to_s
post request_path, request_parts
end
it 'should respond with HTTP Created' do
expect(subject.status).to eq(201)
end
it 'should return the new attachment' do
expect(subject.body).to be_json_eql('Attachment'.to_json).at_path('_type')
end
it 'ignores the original file name' do
expect(subject.body).to be_json_eql('cat.png'.to_json).at_path('fileName')
end
context 'metadata section is missing' do
let(:request_parts) { { file: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'file section is missing' do
# rack-test won't send a multipart request without a file being present
# however as long as we depend on correctly named sections this test should do just fine
let(:request_parts) { { metadata: metadata, wrongFileSection: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'metadata section is no valid JSON' do
let(:metadata) { '"fileName": "cat.png"' }
it_behaves_like 'parse error'
end
context 'metadata is missing the fileName' do
let(:metadata) { Hash.new.to_json }
it_behaves_like 'constraint violation' do
let(:message) { "fileName #{I18n.t('activerecord.errors.messages.blank')}" }
end
end
context 'file is too large' do
let(:file) { mock_uploaded_file(content: 'a' * 2.kilobytes) }
let(:expanded_localization) do
I18n.t('activerecord.errors.messages.file_too_large', count: max_file_size.kilobytes)
end
it_behaves_like 'constraint violation' do
let(:message) { "File #{expanded_localization}" }
end
end
context 'only allowed to add messages, but no edit permission' do
let(:permissions) { %i[view_messages add_messages] }
it_behaves_like 'unauthorized access'
end
context 'only allowed to view messages' do
let(:permissions) { [:view_messages] }
it_behaves_like 'unauthorized access'
end
end
end
@@ -1,140 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Attachments by wiki page resource', type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) do
FactoryBot.create(:user,
member_in_project: project,
member_through_role: role)
end
let(:project) { FactoryBot.create(:project) }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) { [:view_wiki_pages] }
let(:wiki) { FactoryBot.create(:wiki, project: project) }
let(:wiki_page) { FactoryBot.create(:wiki_page, wiki: wiki) }
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.attachments_by_wiki_page wiki_page.id }
before do
FactoryBot.create_list(:attachment, 2, container: wiki_page)
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 2, 2, 'Attachment'
end
describe '#post' do
let(:permissions) { %i[view_wiki_pages edit_wiki_pages] }
let(:request_path) { api_v3_paths.attachments_by_wiki_page wiki_page.id }
let(:request_parts) { { metadata: metadata, file: file } }
let(:metadata) { { fileName: 'cat.png' }.to_json }
let(:file) { mock_uploaded_file(name: 'original-filename.txt') }
let(:max_file_size) { 1 } # given in kiB
before do
allow(Setting).to receive(:attachment_max_size).and_return max_file_size.to_s
post request_path, request_parts
end
it 'should respond with HTTP Created' do
expect(subject.status).to eq(201)
end
it 'should return the new attachment' do
expect(subject.body).to be_json_eql('Attachment'.to_json).at_path('_type')
end
it 'ignores the original file name' do
expect(subject.body).to be_json_eql('cat.png'.to_json).at_path('fileName')
end
context 'metadata section is missing' do
let(:request_parts) { { file: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'file section is missing' do
# rack-test won't send a multipart request without a file being present
# however as long as we depend on correctly named sections this test should do just fine
let(:request_parts) { { metadata: metadata, wrongFileSection: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'metadata section is no valid JSON' do
let(:metadata) { '"fileName": "cat.png"' }
it_behaves_like 'parse error'
end
context 'metadata is missing the fileName' do
let(:metadata) { Hash.new.to_json }
it_behaves_like 'constraint violation' do
let(:message) { "fileName #{I18n.t('activerecord.errors.messages.blank')}" }
end
end
context 'file is too large' do
let(:file) { mock_uploaded_file(content: 'a' * 2.kilobytes) }
let(:expanded_localization) do
I18n.t('activerecord.errors.messages.file_too_large', count: max_file_size.kilobytes)
end
it_behaves_like 'constraint violation' do
let(:message) { "File #{expanded_localization}" }
end
end
context 'only allowed to view wiki pages' do
let(:permissions) { [:view_wiki_pages] }
it_behaves_like 'unauthorized access'
end
end
end
@@ -1,145 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'rack/test'
describe 'API v3 Attachments by work package resource', type: :request do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
include FileHelpers
let(:current_user) do
FactoryBot.create(:user,
member_in_project: project,
member_through_role: role)
end
let(:project) { FactoryBot.create(:project, is_public: false) }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let(:permissions) { [:view_work_packages] }
let(:work_package) { FactoryBot.create(:work_package, author: current_user, project: project) }
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.attachments_by_work_package work_package.id }
before do
FactoryBot.create_list(:attachment, 2, container: work_package)
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
end
it_behaves_like 'API V3 collection response', 2, 2, 'Attachment'
end
describe '#post' do
let(:permissions) { %i[view_work_packages edit_work_packages] }
let(:request_path) { api_v3_paths.attachments_by_work_package work_package.id }
let(:request_parts) { { metadata: metadata, file: file } }
let(:metadata) { { fileName: 'cat.png' }.to_json }
let(:file) { mock_uploaded_file(name: 'original-filename.txt') }
let(:max_file_size) { 1 } # given in kiB
before do
allow(Setting).to receive(:attachment_max_size).and_return max_file_size.to_s
post request_path, request_parts
end
it 'should respond with HTTP Created' do
expect(subject.status).to eq(201)
end
it 'should return the new attachment' do
expect(subject.body).to be_json_eql('Attachment'.to_json).at_path('_type')
end
it 'ignores the original file name' do
expect(subject.body).to be_json_eql('cat.png'.to_json).at_path('fileName')
end
context 'metadata section is missing' do
let(:request_parts) { { file: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'file section is missing' do
# rack-test won't send a multipart request without a file being present
# however as long as we depend on correctly named sections this test should do just fine
let(:request_parts) { { metadata: metadata, wrongFileSection: file } }
it_behaves_like 'invalid request body', I18n.t('api_v3.errors.multipart_body_error')
end
context 'metadata section is no valid JSON' do
let(:metadata) { '"fileName": "cat.png"' }
it_behaves_like 'parse error'
end
context 'metadata is missing the fileName' do
let(:metadata) { Hash.new.to_json }
it_behaves_like 'constraint violation' do
let(:message) { "fileName #{I18n.t('activerecord.errors.messages.blank')}" }
end
end
context 'file is too large' do
let(:file) { mock_uploaded_file(content: 'a' * 2.kilobytes) }
let(:expanded_localization) do
I18n.t('activerecord.errors.messages.file_too_large', count: max_file_size.kilobytes)
end
it_behaves_like 'constraint violation' do
let(:message) { "File #{expanded_localization}" }
end
end
context 'only allowed to add work packages, but no edit permission' do
let(:permissions) { %i[view_work_packages add_work_packages] }
it_behaves_like 'unauthorized access'
end
context 'only allowed to view work packages' do
let(:permissions) { [:view_work_packages] }
it_behaves_like 'unauthorized access'
end
end
end
@@ -0,0 +1,45 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './attachment_resource_shared_examples'
describe "forum message attachments" do
it_behaves_like "an APIv3 attachment resource", include_by_container = false do
let(:attachment_type) { :forum_message }
let(:create_permission) { nil }
let(:read_permission) { nil }
let(:update_permission) { :edit_messages }
let(:forum) { FactoryBot.create(:forum, project: project) }
let(:forum_message) { FactoryBot.create(:message, forum: forum) }
let(:missing_permissions_user) { FactoryBot.create(:user) }
end
end
@@ -0,0 +1,43 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './attachment_resource_shared_examples'
describe "wiki page attachments" do
it_behaves_like "an APIv3 attachment resource" do
let(:attachment_type) { :wiki_page }
let(:create_permission) { nil }
let(:read_permission) { :view_wiki_pages }
let(:update_permission) { %i(delete_wiki_pages_attachments edit_wiki_pages) }
let(:wiki) { FactoryBot.create(:wiki, project: project) }
let(:wiki_page) { FactoryBot.create(:wiki_page, wiki: wiki) }
end
end
@@ -0,0 +1,44 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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-2017 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require_relative './attachment_resource_shared_examples'
describe "work package attachments" do
it_behaves_like "an APIv3 attachment resource" do
let(:attachment_type) { :work_package }
let(:create_permission) { :add_work_packages }
let(:read_permission) { :view_work_packages }
let(:update_permission) { :edit_work_packages }
let(:work_package) do
FactoryBot.create :work_package, author: current_user, project: project
end
end
end