From 664efc191e34ae01c7285f28b35292967d0bd3e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Wed, 22 May 2024 12:21:28 +0200 Subject: [PATCH] Add API endpoints for news --- .rubocop.yml | 9 ++ app/contracts/news/base_contract.rb | 50 +++++++ app/contracts/news/create_contract.rb | 30 +++++ app/contracts/news/delete_contract.rb | 31 +++++ app/contracts/news/update_contract.rb | 30 +++++ app/controllers/news_controller.rb | 29 ++++- app/models/news.rb | 6 + app/services/news/create_service.rb | 31 +++++ app/services/news/delete_service.rb | 30 +++++ app/services/news/set_attributes_service.rb | 43 ++++++ app/services/news/update_service.rb | 31 +++++ .../components/schemas/news_create_model.yml | 72 +++++++++++ docs/api/apiv3/openapi-spec.yml | 2 + docs/api/apiv3/paths/news.yml | 58 +++++++++ docs/api/apiv3/paths/news_item.yml | 122 ++++++++++++++++++ lib/api/v3/news/news_api.rb | 6 + lib/api/v3/news/news_payload_representer.rb | 37 ++++++ lib/api/v3/news/news_representer.rb | 16 +++ lib/api/v3/utilities/path_helper.rb | 1 + spec/contracts/news/create_contract_spec.rb | 39 ++++++ spec/contracts/news/delete_contract_spec.rb | 58 +++++++++ .../news/shared_contract_examples.rb | 46 +++++++ spec/contracts/news/update_contract_spec.rb | 39 ++++++ .../api/v3/news/create_shared_examples.rb | 94 ++++++++++++++ .../api/v3/news/index_resource_spec.rb | 80 ++++++++++++ .../api/v3/news/news_api_create_spec.rb | 58 +++++++++ .../api/v3/news/news_api_delete_spec.rb | 108 ++++++++++++++++ .../api/v3/news/show_resource_examples.rb | 42 ++++++ .../api/v3/news/show_resource_spec.rb | 70 ++++++++++ .../api/v3/news/update_resource_examples.rb | 61 +++++++++ .../api/v3/news/update_resource_spec.rb | 74 +++++++++++ spec/services/news/create_service_spec.rb | 34 +++++ spec/services/news/delete_service_spec.rb | 66 ++++++++++ .../news/set_attributes_service_spec.rb | 115 +++++++++++++++++ spec/services/news/update_service_spec.rb | 34 +++++ 35 files changed, 1645 insertions(+), 7 deletions(-) create mode 100644 app/contracts/news/base_contract.rb create mode 100644 app/contracts/news/create_contract.rb create mode 100644 app/contracts/news/delete_contract.rb create mode 100644 app/contracts/news/update_contract.rb create mode 100644 app/services/news/create_service.rb create mode 100644 app/services/news/delete_service.rb create mode 100644 app/services/news/set_attributes_service.rb create mode 100644 app/services/news/update_service.rb create mode 100644 docs/api/apiv3/components/schemas/news_create_model.yml create mode 100644 lib/api/v3/news/news_payload_representer.rb create mode 100644 spec/contracts/news/create_contract_spec.rb create mode 100644 spec/contracts/news/delete_contract_spec.rb create mode 100644 spec/contracts/news/shared_contract_examples.rb create mode 100644 spec/contracts/news/update_contract_spec.rb create mode 100644 spec/requests/api/v3/news/create_shared_examples.rb create mode 100644 spec/requests/api/v3/news/index_resource_spec.rb create mode 100644 spec/requests/api/v3/news/news_api_create_spec.rb create mode 100644 spec/requests/api/v3/news/news_api_delete_spec.rb create mode 100644 spec/requests/api/v3/news/show_resource_examples.rb create mode 100644 spec/requests/api/v3/news/show_resource_spec.rb create mode 100644 spec/requests/api/v3/news/update_resource_examples.rb create mode 100644 spec/requests/api/v3/news/update_resource_spec.rb create mode 100644 spec/services/news/create_service_spec.rb create mode 100644 spec/services/news/delete_service_spec.rb create mode 100644 spec/services/news/set_attributes_service_spec.rb create mode 100644 spec/services/news/update_service_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index c70f37c8780c..ae9f15b9cfc1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -200,6 +200,15 @@ RSpec/DescribeClass: - 'spec/features/**/*.rb' - 'modules/*/spec/features/**/*.rb' +# Allow number HTTP status codes in specs +RSpecRails/HttpStatus: + Enabled: false + +# have_http_status does not exist on MockResponse +# using it would yield "expected a response object, but an instance of Rack::MockResponse was received" +RSpecRails/HaveHttpStatus: + Enabled: false + # dynamic finders cop clashes with capybara ID cop Rails/DynamicFindBy: Enabled: true diff --git a/app/contracts/news/base_contract.rb b/app/contracts/news/base_contract.rb new file mode 100644 index 000000000000..7d6325f0e9eb --- /dev/null +++ b/app/contracts/news/base_contract.rb @@ -0,0 +1,50 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::BaseContract < ModelContract + include Attachments::ValidateReplacements + + validate :allowed_to_manage + + def self.model + News + end + + attribute :project + attribute :title + attribute :summary + attribute :description + + def allowed_to_manage + return if model.project.nil? + + unless user.allowed_in_project?(:manage_news, model.project) + errors.add :base, :error_unauthorized + end + end +end diff --git a/app/contracts/news/create_contract.rb b/app/contracts/news/create_contract.rb new file mode 100644 index 000000000000..9a0274c59548 --- /dev/null +++ b/app/contracts/news/create_contract.rb @@ -0,0 +1,30 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::CreateContract < News::BaseContract +end diff --git a/app/contracts/news/delete_contract.rb b/app/contracts/news/delete_contract.rb new file mode 100644 index 000000000000..42648cce9abc --- /dev/null +++ b/app/contracts/news/delete_contract.rb @@ -0,0 +1,31 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::DeleteContract < ::DeleteContract + delete_permission :manage_news +end diff --git a/app/contracts/news/update_contract.rb b/app/contracts/news/update_contract.rb new file mode 100644 index 000000000000..04bb5fef4bf0 --- /dev/null +++ b/app/contracts/news/update_contract.rb @@ -0,0 +1,30 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::UpdateContract < News::BaseContract +end diff --git a/app/controllers/news_controller.rb b/app/controllers/news_controller.rb index 92d31cf6c088..0453d233eec8 100644 --- a/app/controllers/news_controller.rb +++ b/app/controllers/news_controller.rb @@ -73,29 +73,44 @@ def new def edit; end def create - @news = News.new(project: @project, author: User.current) - @news.attributes = permitted_params.news - if @news.save + call = News::CreateService + .new(user: current_user) + .call(permitted_params.news.merge(project: @project)) + + if call.success? flash[:notice] = I18n.t(:notice_successful_create) redirect_to controller: "/news", action: "index", project_id: @project else + @news = call.result render action: "new" end end def update - @news.attributes = permitted_params.news - if @news.save + call = News::UpdateService + .new(model: @news, user: current_user) + .call(permitted_params.news.merge(project: @project)) + + if call.success? flash[:notice] = I18n.t(:notice_successful_update) redirect_to action: "show", id: @news else + @news = call.result render action: "edit" end end def destroy - @news.destroy - flash[:notice] = I18n.t(:notice_successful_delete) + call = News::DeleteService + .new(model: @news, user: current_user) + .call + + if call.success? + flash[:notice] = I18n.t(:notice_successful_delete) + else + call.apply_flash_message!(flash) + end + redirect_to action: "index", project_id: @project end diff --git a/app/models/news.rb b/app/models/news.rb index 202723aee466..eced61e2b18d 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -33,6 +33,7 @@ class News < ApplicationRecord order(:created_at) }, as: :commented, dependent: :delete_all + validates :project, presence: true validates :title, presence: true validates :title, length: { maximum: 256 } validates :summary, length: { maximum: 255 } @@ -46,6 +47,11 @@ class News < ApplicationRecord references: :projects, date_column: "#{table_name}.created_at" + acts_as_attachable view_permission: :view_news, + add_on_new_permission: :manage_news, + add_on_persisted_permission: :manage_news, + delete_permission: :manage_news + acts_as_watchable after_create :add_author_as_watcher diff --git a/app/services/news/create_service.rb b/app/services/news/create_service.rb new file mode 100644 index 000000000000..97af280e9af4 --- /dev/null +++ b/app/services/news/create_service.rb @@ -0,0 +1,31 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::CreateService < BaseServices::Create + include Attachments::ReplaceAttachments +end diff --git a/app/services/news/delete_service.rb b/app/services/news/delete_service.rb new file mode 100644 index 000000000000..04d7b5381c83 --- /dev/null +++ b/app/services/news/delete_service.rb @@ -0,0 +1,30 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::DeleteService < BaseServices::Delete +end diff --git a/app/services/news/set_attributes_service.rb b/app/services/news/set_attributes_service.rb new file mode 100644 index 000000000000..b6274c28460c --- /dev/null +++ b/app/services/news/set_attributes_service.rb @@ -0,0 +1,43 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::SetAttributesService < BaseServices::SetAttributes + include Attachments::SetReplacements + + private + + def set_default_attributes(*) + set_default_author + end + + def set_default_author + model.change_by_system do + model.author = user + end + end +end diff --git a/app/services/news/update_service.rb b/app/services/news/update_service.rb new file mode 100644 index 000000000000..d0e1ef90f959 --- /dev/null +++ b/app/services/news/update_service.rb @@ -0,0 +1,31 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +class News::UpdateService < BaseServices::Update + include Attachments::ReplaceAttachments +end diff --git a/docs/api/apiv3/components/schemas/news_create_model.yml b/docs/api/apiv3/components/schemas/news_create_model.yml new file mode 100644 index 000000000000..fa0fe496cf87 --- /dev/null +++ b/docs/api/apiv3/components/schemas/news_create_model.yml @@ -0,0 +1,72 @@ +# Schema: NewsCreateModel +--- +type: object +properties: + title: + type: string + description: The headline of the news + readOnly: true + summary: + type: string + description: A short summary + readOnly: true + description: + type: string + description: The main body of the news with all the details + readOnly: true + createdAt: + type: string + format: date-time + description: The time the news was created at + readOnly: true + _links: + type: object + required: + - project + - author + properties: + project: + allOf: + - "$ref": "./link.yml" + - description: |- + The project the news is situated in + + **Resource**: Project + author: + allOf: + - "$ref": "./link.yml" + - description: |- + The user having created the news + + **Resource**: User + readOnly: true +example: + _type: News + title: asperiores possimus nam doloribus ab + summary: Celebrer spiculum colo viscus claustrum atque. Id nulla culpa sumptus. + Comparo crapula depopulo demonstro. + description: + format: markdown + raw: Videlicet deserunt aequitas cognatus. Concedo quia est quia pariatur vorago + vallum. Calco autem atavus accusamus conscendo cornu ulterius. Tam patria ago + consectetur ventito sustineo nihil caecus. Supra officiis eos velociter somniculosus + tonsor qui. Suffragium aduro arguo angustus cogito quia tolero vulnus. Supplanto + sortitus cresco apud vestrum qui. + html: "

Videlicet deserunt aequitas cognatus. Concedo quia est quia pariatur + vorago vallum. Calco autem atavus accusamus conscendo cornu ulterius. Tam patria + ago consectetur ventito sustineo nihil caecus. Supra officiis eos velociter + somniculosus tonsor qui. Suffragium aduro arguo angustus cogito quia tolero + vulnus. Supplanto sortitus cresco apud vestrum qui.

" + createdAt: '2015-03-20T12:57:01.908Z' + _links: + project: + href: "/api/v3/projects/1" + title: A project + author: + href: "/api/v3/users/2" + title: Peggie Feeney + _embedded: + project: + _type: Project... + author: + _type: User... diff --git a/docs/api/apiv3/openapi-spec.yml b/docs/api/apiv3/openapi-spec.yml index be010effa5c4..d70c2d523e3b 100644 --- a/docs/api/apiv3/openapi-spec.yml +++ b/docs/api/apiv3/openapi-spec.yml @@ -665,6 +665,8 @@ components: "$ref": "./components/schemas/membership_schema_model.yml" MembershipWriteModel: "$ref": "./components/schemas/membership_write_model.yml" + NewsCreateModel: + "$ref": "./components/schemas/news_create_model.yml" NewsModel: "$ref": "./components/schemas/news_model.yml" NonWorkingDayCollectionModel: diff --git a/docs/api/apiv3/paths/news.yml b/docs/api/apiv3/paths/news.yml index 9908ee74a426..bf362bafd833 100644 --- a/docs/api/apiv3/paths/news.yml +++ b/docs/api/apiv3/paths/news.yml @@ -148,3 +148,61 @@ get: also on the requesting user's permissions. operationId: List_News summary: List News + +post: + summary: Create News + operationId: create_news + tags: + - News + description: |- + Creates a news entry. Only administrators and users with "Manage news" permission in the given project are eligible. + When calling this endpoint the client provides a single object, containing at least the properties and links that are required, in the body. + requestBody: + content: + application/json: + schema: + $ref: '../components/schemas/news_create_model.yml' + responses: + '201': + content: + application/hal+json: + schema: + $ref: '../components/schemas/news_model.yml' + description: Created + '400': + $ref: "../components/responses/invalid_request_body.yml" + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to create new news. + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission:** Administrator, Manage news permission in the project + '406': + $ref: "../components/responses/missing_content_type.yml" + '415': + $ref: "../components/responses/unsupported_media_type.yml" + '422': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _embedded: + details: + attribute: title + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:PropertyConstraintViolation + message: Title can't be blank. + description: |- + Returned if: + + * the client tries to modify a read-only property (`PropertyIsReadOnly`) + + * a constraint for a property was violated (`PropertyConstraintViolation`) diff --git a/docs/api/apiv3/paths/news_item.yml b/docs/api/apiv3/paths/news_item.yml index 018e15a1015d..5f639d5fee8b 100644 --- a/docs/api/apiv3/paths/news_item.yml +++ b/docs/api/apiv3/paths/news_item.yml @@ -76,3 +76,125 @@ get: description: '' operationId: View_news summary: View news +patch: + summary: Update news + operationId: update_news + tags: + - News + description: |- + Updates the news's writable attributes. + When calling this endpoint the client provides a single object, containing at least the properties and links that are required, in the body. + parameters: + - description: News id + example: '1' + in: path + name: id + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '../components/schemas/news_model.yml' + responses: + '200': + content: + application/hal+json: + schema: + $ref: '../components/schemas/news_model.yml' + description: OK + '400': + $ref: "../components/responses/invalid_request_body.yml" + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to update this news. + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission:** Administrators, Manage news permission + '404': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The specified news does not exist or you do not have permission to view it. + description: |- + Returned if the news entry does not exist or if the API user does not have the necessary permissions to update it. + + **Required permission:** Administrators, Manage news permission + '406': + $ref: "../components/responses/missing_content_type.yml" + '415': + $ref: "../components/responses/unsupported_media_type.yml" + '422': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _embedded: + details: + attribute: title + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:PropertyConstraintViolation + message: Title can't be blank. + description: |- + Returned if: + + * the client tries to modify a read-only property (`PropertyIsReadOnly`) + + * a constraint for a property was violated (`PropertyConstraintViolation`) +delete: + summary: Delete news + operationId: delete_news + description: Permanently deletes the specified news entry. + tags: + - News + parameters: + - description: News id + example: '1' + in: path + name: id + required: true + schema: + type: integer + responses: + '202': + description: |- + Returned if the news was deleted successfully. + + Note that the response body is empty as of now. In future versions of the API a body + *might* be returned, indicating the progress of deletion. + '403': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission + message: You are not allowed to delete this news entry + description: |- + Returned if the client does not have sufficient permissions. + + **Required permission:** Administrators and Manage news permission + '404': + content: + application/hal+json: + schema: + $ref: '../components/schemas/error_response.yml' + example: + _type: Error + errorIdentifier: urn:openproject-org:api:v3:errors:NotFound + message: The specified news does not exist. + description: Returned if the news does not exist. diff --git a/lib/api/v3/news/news_api.rb b/lib/api/v3/news/news_api.rb index b9012762f6ad..d47ba3fb1264 100644 --- a/lib/api/v3/news/news_api.rb +++ b/lib/api/v3/news/news_api.rb @@ -36,6 +36,10 @@ class NewsAPI < ::API::OpenProjectAPI self_path: :newses) .mount + post &::API::V3::Utilities::Endpoints::Create + .new(model: News) + .mount + route_param :id, type: Integer, desc: "News ID" do after_validation do @news = ::News @@ -46,6 +50,8 @@ class NewsAPI < ::API::OpenProjectAPI get &::API::V3::Utilities::Endpoints::Show .new(model: ::News) .mount + patch &::API::V3::Utilities::Endpoints::Update.new(model: ::News).mount + delete &::API::V3::Utilities::Endpoints::Delete.new(model: ::News, success_status: 204).mount end end end diff --git a/lib/api/v3/news/news_payload_representer.rb b/lib/api/v3/news/news_payload_representer.rb new file mode 100644 index 000000000000..56f9184a9aab --- /dev/null +++ b/lib/api/v3/news/news_payload_representer.rb @@ -0,0 +1,37 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +module API + module V3 + module News + class NewsPayloadRepresenter < NewsRepresenter + include ::API::Utilities::PayloadRepresenter + end + end + end +end diff --git a/lib/api/v3/news/news_representer.rb b/lib/api/v3/news/news_representer.rb index 968322c832d7..d917b86caa60 100644 --- a/lib/api/v3/news/news_representer.rb +++ b/lib/api/v3/news/news_representer.rb @@ -63,6 +63,22 @@ class NewsRepresenter < ::API::Decorators::Single v3_path: :user, representer: ::API::V3::Users::UserRepresenter + link :updateImmediately, + cache_if: -> { current_user.allowed_in_project?(:manage_news, represented.project) } do + { + href: api_v3_paths.news(represented.id), + method: :patch + } + end + + link :delete, + cache_if: -> { current_user.allowed_in_project?(:manage_news, represented.project) } do + { + href: api_v3_paths.news(represented.id), + method: :delete + } + end + def _type "News" end diff --git a/lib/api/v3/utilities/path_helper.rb b/lib/api/v3/utilities/path_helper.rb index 75ada6df0744..bfe5c673ad5c 100644 --- a/lib/api/v3/utilities/path_helper.rb +++ b/lib/api/v3/utilities/path_helper.rb @@ -243,6 +243,7 @@ def self.memberships_available_projects show :message index :newses, :news + show :news def self.news(id) "#{newses}/#{id}" diff --git a/spec/contracts/news/create_contract_spec.rb b/spec/contracts/news/create_contract_spec.rb new file mode 100644 index 000000000000..0c6799f31bb2 --- /dev/null +++ b/spec/contracts/news/create_contract_spec.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require_relative "shared_contract_examples" + +RSpec.describe News::CreateContract do + include_context "ModelContract shared context" + + it_behaves_like "news contract" do + let(:news) { build(:news, title: "Test", project:) } + let(:contract) { described_class.new(news, current_user) } + end +end diff --git a/spec/contracts/news/delete_contract_spec.rb b/spec/contracts/news/delete_contract_spec.rb new file mode 100644 index 000000000000..36c14d0d58af --- /dev/null +++ b/spec/contracts/news/delete_contract_spec.rb @@ -0,0 +1,58 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.describe News::DeleteContract do + include_context "ModelContract shared context" + + shared_let(:project) { create(:project) } + shared_let(:news) { create(:news, project:) } + + let(:contract) { described_class.new(news, current_user) } + + it_behaves_like "contract is valid for active admins and invalid for regular users" + + context "when user with permission in project" do + let(:current_user) { create(:user, member_with_permissions: { project => %i[manage_news] }) } + + it_behaves_like "contract is valid" + end + + context "when user with permission in other_project" do + let(:other_project) { create(:project) } + let(:current_user) { create(:user, member_with_permissions: { other_project => %i[manage_news] }) } + + it_behaves_like "contract is invalid", base: :error_unauthorized + end + + include_examples "contract reuses the model errors" do + let(:current_user) { build_stubbed(:admin) } + end +end diff --git a/spec/contracts/news/shared_contract_examples.rb b/spec/contracts/news/shared_contract_examples.rb new file mode 100644 index 000000000000..3417dc4b2ce2 --- /dev/null +++ b/spec/contracts/news/shared_contract_examples.rb @@ -0,0 +1,46 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "contracts/shared/model_contract_shared_context" + +RSpec.shared_examples_for "news contract" do + shared_let(:project) { create(:project) } + + context "when user with permission" do + let(:current_user) { create(:user, member_with_permissions: { project => %i[view_news manage_news] }) } + + it_behaves_like "contract is valid" + end + + it_behaves_like "contract is valid for active admins and invalid for regular users" + + include_examples "contract reuses the model errors" do + let(:current_user) { build_stubbed(:admin) } + end +end diff --git a/spec/contracts/news/update_contract_spec.rb b/spec/contracts/news/update_contract_spec.rb new file mode 100644 index 000000000000..c3779aea6b49 --- /dev/null +++ b/spec/contracts/news/update_contract_spec.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require_relative "shared_contract_examples" + +RSpec.describe News::UpdateContract do + include_context "ModelContract shared context" + + it_behaves_like "news contract" do + let(:news) { build_stubbed(:news, project:) } + let(:contract) { described_class.new(news, current_user) } + end +end diff --git a/spec/requests/api/v3/news/create_shared_examples.rb b/spec/requests/api/v3/news/create_shared_examples.rb new file mode 100644 index 000000000000..b8015d7de0f0 --- /dev/null +++ b/spec/requests/api/v3/news/create_shared_examples.rb @@ -0,0 +1,94 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +RSpec.shared_context "create news request context" do + include API::V3::Utilities::PathHelper + + let(:parameters) do + { + title: "My news entry", + summary: "Hello from API", + _links: { + project: { + href: api_v3_paths.project(project.id) + } + } + } + end + + let(:send_request) do + header "Content-Type", "application/json" + post api_v3_paths.newses, parameters.to_json + end + + let(:parsed_response) { JSON.parse(last_response.body) } +end + +RSpec.shared_examples "create news request flow" do + include_context "create news request context" + + describe "empty request body" do + let(:parameters) { {} } + + it "returns an erroneous response" do + send_request + + expect(last_response.status).to eq(422) + expect(last_response.body) + .to be_json_eql("urn:openproject-org:api:v3:errors:MultipleErrors".to_json) + .at_path("errorIdentifier") + end + end + + it "creates the news when valid" do + send_request + + expect(last_response.status).to eq(201) + news = News.find_by(title: parameters[:title]) + expect(news).to be_present + expect(news.project).to eq(project) + expect(news.author).to eq(user) + end + + describe "when the title is missing" do + it "returns an error" do + header "Content-Type", "application/json" + post api_v3_paths.newses, parameters.except(:title).to_json + + expect(last_response.status).to eq(422) + expect(last_response.body) + .to be_json_eql("urn:openproject-org:api:v3:errors:PropertyConstraintViolation".to_json) + .at_path("errorIdentifier") + + expect(parsed_response["_embedded"]["details"]["attribute"]) + .to eq "title" + + expect(parsed_response["message"]) + .to eq "Title can't be blank." + end + end +end diff --git a/spec/requests/api/v3/news/index_resource_spec.rb b/spec/requests/api/v3/news/index_resource_spec.rb new file mode 100644 index 000000000000..7065f5d04827 --- /dev/null +++ b/spec/requests/api/v3/news/index_resource_spec.rb @@ -0,0 +1,80 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +require "spec_helper" +require "rack/test" + +RSpec.describe API::V3::News::NewsAPI, + "index", + content_type: :json do + include API::V3::Utilities::PathHelper + + shared_let(:project1) { create(:project) } + shared_let(:project2) { create(:project) } + shared_let(:news1) { create(:news, project: project1) } + shared_let(:news2) { create(:news, project: project2) } + + let(:send_request) do + get api_v3_paths.newses + end + + let(:parsed_response) { JSON.parse(last_response.body) } + + current_user { user } + + before do + send_request + end + + context "for an admin user" do + let(:user) { build(:admin) } + + it_behaves_like "API V3 collection response", 2, 2, "News" + end + + context "for a user with view_news permissions in one project" do + let(:user) { create(:user, member_with_permissions: { project1 => %i[view_news]}) } + + it_behaves_like "API V3 collection response", 1, 1, "News" + + it "returns only the news in the visible project" do + expect(last_response.body) + .to be_json_eql(api_v3_paths.news(news1.id).to_json) + .at_path("_embedded/elements/0/_links/self/href") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.project(project1.id).to_json) + .at_path("_embedded/elements/0/_links/project/href") + end + end + + context "for an unauthorized user" do + let(:user) { build(:user) } + + it_behaves_like "API V3 collection response", 0, 0, "News" + end +end diff --git a/spec/requests/api/v3/news/news_api_create_spec.rb b/spec/requests/api/v3/news/news_api_create_spec.rb new file mode 100644 index 000000000000..b49c8dfb7809 --- /dev/null +++ b/spec/requests/api/v3/news/news_api_create_spec.rb @@ -0,0 +1,58 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +require "spec_helper" +require "rack/test" +require_relative "create_shared_examples" + +RSpec.describe API::V3::News::NewsAPI, "create" do + include_context "create news request context" + shared_let(:project) { create(:project, enabled_module_names: %w[news]) } + current_user { user } + + describe "admin user" do + let(:user) { build(:admin) } + + it_behaves_like "create news request flow" + end + + describe "user with manage_news permission" do + let(:user) { create(:user, member_with_permissions: { project => %i[view_news manage_news] }) } + + it_behaves_like "create news request flow" + end + + describe "unauthorized user" do + let(:user) { create(:user, member_with_permissions: { project => %i[view_news] }) } + + it "returns an erroneous response" do + send_request + + expect(last_response.status).to eq(403) + end + end +end diff --git a/spec/requests/api/v3/news/news_api_delete_spec.rb b/spec/requests/api/v3/news/news_api_delete_spec.rb new file mode 100644 index 000000000000..fd0fa2f59593 --- /dev/null +++ b/spec/requests/api/v3/news/news_api_delete_spec.rb @@ -0,0 +1,108 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +require "spec_helper" + +RSpec.describe API::V3::News::NewsAPI, "delete" do + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project) } + shared_let(:news) { create(:news, project:, title: "foo") } + + let(:send_request) do + header "Content-Type", "application/json" + end + + let(:path) { api_v3_paths.news(news.id) } + let(:parsed_response) { JSON.parse(last_response.body) } + + current_user { user } + + RSpec.shared_examples "deletion allowed" do + it "deletes the news" do + expect(last_response.status).to eq 204 + expect { news.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + context "with a non-existent news" do + let(:path) { api_v3_paths.news 1337 } + + it_behaves_like "not found" + end + end + + RSpec.shared_examples "deletion is not allowed" do |status| + it "does not delete the user" do + expect(last_response.status).to eq status + expect(News).to exist(news.id) + end + end + + before do + header "Content-Type", "application/json" + delete path + end + + context "when admin" do + let(:user) { build_stubbed(:admin) } + + it_behaves_like "deletion allowed" + end + + context "when locked admin" do + let(:user) { build_stubbed(:admin, status: Principal.statuses[:locked]) } + + it_behaves_like "deletion is not allowed", 404 + end + + context "when non-admin" do + let(:user) { build_stubbed(:user, admin: false) } + + it_behaves_like "deletion is not allowed", 404 + end + + context "when user with manage_news permission" do + let(:user) { create(:user, member_with_permissions: { project => %i[view_news manage_news] }) } + + it_behaves_like "deletion allowed" + end + + context "when anonymous user" do + let(:user) { create(:anonymous) } + + context "when login_required", with_settings: { login_required: true } do + it_behaves_like "error response", + 401, + "Unauthenticated", + I18n.t("api_v3.errors.code_401") + end + + context "when not login_required", with_settings: { login_required: false } do + it_behaves_like "deletion is not allowed", 404 + end + end +end diff --git a/spec/requests/api/v3/news/show_resource_examples.rb b/spec/requests/api/v3/news/show_resource_examples.rb new file mode 100644 index 000000000000..49ce4544beae --- /dev/null +++ b/spec/requests/api/v3/news/show_resource_examples.rb @@ -0,0 +1,42 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +RSpec.shared_examples "represents the news" do + it do + aggregate_failures do + expect(last_response.status).to eq(200) + expect(last_response.body) + .to(be_json_eql("News".to_json).at_path("_type")) + + expect(last_response.body) + .to(be_json_eql(news.title.to_json).at_path("title")) + + expect(last_response.body) + .to(be_json_eql(news.id.to_json).at_path("id")) + end + end +end diff --git a/spec/requests/api/v3/news/show_resource_spec.rb b/spec/requests/api/v3/news/show_resource_spec.rb new file mode 100644 index 000000000000..63c5638ab530 --- /dev/null +++ b/spec/requests/api/v3/news/show_resource_spec.rb @@ -0,0 +1,70 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +require "spec_helper" +require_relative "show_resource_examples" + +RSpec.describe API::V3::News::NewsAPI, + "show" do + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project) } + shared_let(:news) { create(:news, title: "foo", project:) } + + let(:send_request) do + header "Content-Type", "application/json" + get api_v3_paths.news(news.id) + end + + let(:parsed_response) { JSON.parse(last_response.body) } + + current_user { user } + + before do + send_request + end + + describe "admin user" do + let(:user) { build(:admin) } + + it_behaves_like "represents the news" + end + + describe "user with manage_news_user permission" do + let(:user) { create(:user, member_with_permissions: { project => %i[view_news] }) } + + it_behaves_like "represents the news" + end + + describe "unauthorized user" do + let(:user) { build(:user) } + + it "returns a 404 response" do + expect(last_response.status).to eq(404) + end + end +end diff --git a/spec/requests/api/v3/news/update_resource_examples.rb b/spec/requests/api/v3/news/update_resource_examples.rb new file mode 100644 index 000000000000..3fbbca965b39 --- /dev/null +++ b/spec/requests/api/v3/news/update_resource_examples.rb @@ -0,0 +1,61 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +RSpec.shared_examples "updates the news" do + context "with an empty title" do + let(:parameters) do + { title: "" } + end + + it "returns an error" do + expect(last_response.status).to eq(422) + expect(last_response.body) + .to be_json_eql("urn:openproject-org:api:v3:errors:PropertyConstraintViolation".to_json) + .at_path("errorIdentifier") + + expect(parsed_response["_embedded"]["details"]["attribute"]) + .to eq "title" + + expect(parsed_response["message"]) + .to eq "Title can't be blank." + end + end + + context "with a new title" do + let(:parameters) do + { title: "my new title" } + end + + it "updates the news" do + expect(last_response.status).to eq(200) + + news.reload + + expect(news.title).to eq "my new title" + end + end +end diff --git a/spec/requests/api/v3/news/update_resource_spec.rb b/spec/requests/api/v3/news/update_resource_spec.rb new file mode 100644 index 000000000000..f2fe8f97684f --- /dev/null +++ b/spec/requests/api/v3/news/update_resource_spec.rb @@ -0,0 +1,74 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. + +require "spec_helper" +require_relative "update_resource_examples" + +RSpec.describe API::V3::News::NewsAPI, + "update" do + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project) } + shared_let(:news) { create(:news, title: "foo", project:) } + + let(:parameters) do + {} + end + + let(:send_request) do + header "Content-Type", "application/json" + patch api_v3_paths.news(news.id), parameters.to_json + end + + let(:parsed_response) { JSON.parse(last_response.body) } + + current_user { user } + + before do + send_request + end + + describe "admin user" do + let(:user) { create(:admin) } + + it_behaves_like "updates the news" + end + + describe "user with manage_news permission" do + let(:user) { create(:user, member_with_permissions: { project => %i[view_news manage_news] }) } + + it_behaves_like "updates the news" + end + + describe "unauthorized user" do + let(:user) { build(:user) } + + it "returns a 404 response" do + expect(last_response.status).to eq(404) + end + end +end diff --git a/spec/services/news/create_service_spec.rb b/spec/services/news/create_service_spec.rb new file mode 100644 index 000000000000..df91f6f9d781 --- /dev/null +++ b/spec/services/news/create_service_spec.rb @@ -0,0 +1,34 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "services/base_services/behaves_like_create_service" + +RSpec.describe News::CreateService, type: :model do + it_behaves_like "BaseServices create service" +end diff --git a/spec/services/news/delete_service_spec.rb b/spec/services/news/delete_service_spec.rb new file mode 100644 index 000000000000..dc0a20399c0f --- /dev/null +++ b/spec/services/news/delete_service_spec.rb @@ -0,0 +1,66 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" + +RSpec.describe News::DeleteService, type: :model do + let(:news) { build_stubbed(:news, project:) } + let(:project) { build_stubbed(:project) } + + let(:instance) { described_class.new(model: news, user: actor) } + + subject do + instance.call + end + + shared_examples "deletes the news" do + it do + expect(news).to receive(:destroy).and_return(true) + expect(subject).to be_success + end + end + + shared_examples "does not delete the news" do + it do + expect(news).not_to receive(:destroy) + expect(subject).not_to be_success + end + end + + context "with allowed user" do + let(:actor) { build_stubbed(:user) } + + before do + mock_permissions_for(actor) do |mock| + mock.allow_in_project(:manage_news, project:) + end + end + + it_behaves_like "deletes the news" + end +end diff --git a/spec/services/news/set_attributes_service_spec.rb b/spec/services/news/set_attributes_service_spec.rb new file mode 100644 index 000000000000..dea177d09995 --- /dev/null +++ b/spec/services/news/set_attributes_service_spec.rb @@ -0,0 +1,115 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" + +RSpec.describe News::SetAttributesService, type: :model do + let(:current_user) { build_stubbed(:user) } + + let(:contract_instance) do + contract = double("contract_instance") + allow(contract) + .to receive(:validate) + .and_return(contract_valid) + allow(contract) + .to receive(:errors) + .and_return(contract_errors) + contract + end + + let(:contract_errors) { double("contract_errors") } + let(:contract_valid) { true } + let(:model_valid) { true } + + let(:instance) do + described_class.new(user: current_user, + model: model_instance, + contract_class:, + contract_options: {}) + end + let(:model_instance) { News.new } + let(:contract_class) do + allow(News::CreateContract) + .to receive(:new) + .and_return(contract_instance) + + News::CreateContract + end + + let(:params) { {} } + + before do + allow(model_instance) + .to receive(:valid?) + .and_return(model_valid) + end + + subject { instance.call(params) } + + it "returns the instance as the result" do + expect(subject.result) + .to eql model_instance + end + + it "is a success" do + expect(subject) + .to be_success + end + + context "with params" do + let(:params) do + { + title: "Foobar" + } + end + + it "assigns the params" do + subject + + expect(model_instance.title).to eq "Foobar" + end + end + + context "with an invalid contract" do + let(:contract_valid) { false } + let(:expect_time_instance_save) do + expect(model_instance) + .not_to receive(:save) + end + + it "returns failure" do + expect(subject) + .not_to be_success + end + + it "returns the contract's errors" do + expect(subject.errors) + .to eql(contract_errors) + end + end +end diff --git a/spec/services/news/update_service_spec.rb b/spec/services/news/update_service_spec.rb new file mode 100644 index 000000000000..e1d2fbd813f9 --- /dev/null +++ b/spec/services/news/update_service_spec.rb @@ -0,0 +1,34 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# 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 COPYRIGHT and LICENSE files for more details. +#++ + +require "spec_helper" +require "services/base_services/behaves_like_update_service" + +RSpec.describe News::UpdateService, type: :model do + it_behaves_like "BaseServices update service" +end