diff --git a/modules/meeting/lib/api/v3/meeting_agenda_items/agenda_items_by_meeting_api.rb b/modules/meeting/lib/api/v3/meeting_agenda_items/agenda_items_by_meeting_api.rb index c9b1f484bc5..5fc7f2dcb1a 100644 --- a/modules/meeting/lib/api/v3/meeting_agenda_items/agenda_items_by_meeting_api.rb +++ b/modules/meeting/lib/api/v3/meeting_agenda_items/agenda_items_by_meeting_api.rb @@ -36,17 +36,10 @@ module API get do items = @meeting.agenda_items.includes(:author, :presenter, :work_package, :meeting_section) MeetingAgendaItemCollectionRepresenter.new(items, - self_link: api_v3_paths.meeting_agenda_items(@meeting.id), + self_link: api_v3_paths.meeting_agenda_items(meeting_id: @meeting.id), current_user:) end - post(&::API::V3::Utilities::Endpoints::Create - .new(model: MeetingAgendaItem, - params_modifier: ->(params) { - params.except(:meeting, :meeting_id).merge(meeting: @meeting) - }) - .mount) - route_param :agenda_item_id, type: Integer, desc: "Agenda item ID" do after_validation do @meeting_agenda_item = @meeting.agenda_items.find(declared_params[:agenda_item_id]) @@ -54,10 +47,6 @@ module API get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingAgendaItem).mount - patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingAgendaItem).mount - - delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingAgendaItem).mount - mount ::API::V3::MeetingOutcomes::OutcomesByAgendaItemAPI end end diff --git a/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_item_representer.rb b/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_item_representer.rb index 16d41286ba6..9a23182b7ab 100644 --- a/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_item_representer.rb +++ b/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_item_representer.rb @@ -42,8 +42,7 @@ module API { outcomes: %i[author work_package] } ] - self_link id_attribute: ->(*) { [represented.meeting_id, represented.id] }, - title_getter: ->(*) { represented.title } + self_link title_getter: ->(*) { represented.title } property :id @@ -88,7 +87,7 @@ module API next if represented.meeting_section_id.nil? { - href: api_v3_paths.meeting_section(represented.meeting_id, represented.meeting_section_id), + href: api_v3_paths.meeting_section(represented.meeting_section_id), title: represented.meeting_section&.title } } @@ -102,8 +101,7 @@ module API link: ->(*) { represented.outcomes.map do |outcome| { - href: api_v3_paths - .meeting_agenda_item_outcome(represented.meeting_id, represented.id, outcome.id), + href: api_v3_paths.meeting_outcome(outcome.id), title: outcome.id.to_s } end diff --git a/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_items_api.rb b/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_items_api.rb new file mode 100644 index 00000000000..a1936b3c0a6 --- /dev/null +++ b/modules/meeting/lib/api/v3/meeting_agenda_items/meeting_agenda_items_api.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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 MeetingAgendaItems + class MeetingAgendaItemsAPI < ::API::OpenProjectAPI + resources :meeting_agenda_items do + post &::API::V3::Utilities::Endpoints::Create.new(model: MeetingAgendaItem).mount + + route_param :id, type: Integer, desc: "Agenda item ID" do + after_validation do + @meeting_agenda_item = MeetingAgendaItem + .joins(meeting: :project) + .merge(Meeting.visible) + .find(declared_params[:id]) + end + + get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingAgendaItem).mount + + patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingAgendaItem).mount + + delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingAgendaItem).mount + end + end + end + end + end +end diff --git a/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcome_representer.rb b/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcome_representer.rb index 153353f19fe..dac02c36edb 100644 --- a/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcome_representer.rb +++ b/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcome_representer.rb @@ -39,10 +39,7 @@ module API self.to_eager_load = [{ meeting_agenda_item: :meeting }, :author, :work_package] - self_link path: :meeting_agenda_item_outcome, - id_attribute: ->(*) { - [represented.meeting_agenda_item.meeting_id, represented.meeting_agenda_item_id, represented.id] - }, + self_link path: :meeting_outcome, title_getter: ->(*) { represented.id.to_s } property :id @@ -56,13 +53,16 @@ module API representer: ::API::V3::Users::UserRepresenter, skip_render: ->(*) { represented.author_id.nil? } - link :agendaItem do - { - href: api_v3_paths.meeting_agenda_item(represented.meeting_agenda_item.meeting_id, - represented.meeting_agenda_item_id), - title: represented.meeting_agenda_item.title - } - end + associated_resource :meeting_agenda_item, + as: :agendaItem, + link: ->(*) { + next if represented.meeting_agenda_item_id.nil? + + { + href: api_v3_paths.meeting_agenda_item(represented.meeting_agenda_item_id), + title: represented.meeting_agenda_item.title + } + } associated_visible_resource :work_package diff --git a/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcomes_api.rb b/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcomes_api.rb new file mode 100644 index 00000000000..8101c85217f --- /dev/null +++ b/modules/meeting/lib/api/v3/meeting_outcomes/meeting_outcomes_api.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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 MeetingOutcomes + class MeetingOutcomesAPI < ::API::OpenProjectAPI + resources :meeting_outcomes do + post &::API::V3::Utilities::Endpoints::Create.new(model: MeetingOutcome).mount + + route_param :id, type: Integer, desc: "Outcome ID" do + after_validation do + @meeting_outcome = MeetingOutcome + .joins(meeting_agenda_item: { meeting: :project }) + .merge(Meeting.visible) + .find(declared_params[:id]) + end + + get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingOutcome).mount + + patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingOutcome).mount + + delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingOutcome).mount + end + end + end + end + end +end diff --git a/modules/meeting/lib/api/v3/meeting_outcomes/outcomes_by_agenda_item_api.rb b/modules/meeting/lib/api/v3/meeting_outcomes/outcomes_by_agenda_item_api.rb index 9ab379fad82..298e54d3421 100644 --- a/modules/meeting/lib/api/v3/meeting_outcomes/outcomes_by_agenda_item_api.rb +++ b/modules/meeting/lib/api/v3/meeting_outcomes/outcomes_by_agenda_item_api.rb @@ -38,29 +38,17 @@ module API MeetingOutcomeCollectionRepresenter.new(outcomes, self_link: api_v3_paths - .meeting_agenda_item_outcomes(@meeting.id, @meeting_agenda_item.id), + .meeting_agenda_item_outcomes(@meeting_agenda_item.id, + meeting_id: @meeting.id), current_user:) end - post(&::API::V3::Utilities::Endpoints::Create - .new(model: MeetingOutcome, - params_modifier: ->(params) { - params - .except(:meeting_agenda_item, :meeting_agenda_item_id) - .merge(meeting_agenda_item: @meeting_agenda_item) - }) - .mount) - route_param :outcome_id, type: Integer, desc: "Outcome ID" do after_validation do @meeting_outcome = @meeting_agenda_item.outcomes.find(declared_params[:outcome_id]) end get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingOutcome).mount - - patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingOutcome).mount - - delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingOutcome).mount end end end diff --git a/modules/meeting/lib/api/v3/meeting_sections/meeting_section_representer.rb b/modules/meeting/lib/api/v3/meeting_sections/meeting_section_representer.rb index 79b6c768f62..3ffef01b4fe 100644 --- a/modules/meeting/lib/api/v3/meeting_sections/meeting_section_representer.rb +++ b/modules/meeting/lib/api/v3/meeting_sections/meeting_section_representer.rb @@ -38,8 +38,7 @@ module API self.to_eager_load = [:meeting] - self_link id_attribute: ->(*) { [represented.meeting_id, represented.id] }, - title_getter: ->(*) { represented.title } + self_link title_getter: ->(*) { represented.title } property :id diff --git a/modules/meeting/lib/api/v3/meeting_sections/meeting_sections_api.rb b/modules/meeting/lib/api/v3/meeting_sections/meeting_sections_api.rb new file mode 100644 index 00000000000..4434c35ea40 --- /dev/null +++ b/modules/meeting/lib/api/v3/meeting_sections/meeting_sections_api.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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 MeetingSections + class MeetingSectionsAPI < ::API::OpenProjectAPI + resources :meeting_sections do + post &::API::V3::Utilities::Endpoints::Create.new(model: MeetingSection).mount + + route_param :id, type: Integer, desc: "Section ID" do + after_validation do + @meeting_section = MeetingSection + .joins(meeting: :project) + .merge(Meeting.visible) + .find(declared_params[:id]) + end + + get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingSection).mount + + patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingSection).mount + + delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingSection).mount + end + end + end + end + end +end diff --git a/modules/meeting/lib/api/v3/meeting_sections/sections_by_meeting_api.rb b/modules/meeting/lib/api/v3/meeting_sections/sections_by_meeting_api.rb index efe1479a4d6..ace20bb5f02 100644 --- a/modules/meeting/lib/api/v3/meeting_sections/sections_by_meeting_api.rb +++ b/modules/meeting/lib/api/v3/meeting_sections/sections_by_meeting_api.rb @@ -36,27 +36,16 @@ module API get do sections = @meeting.sections MeetingSectionCollectionRepresenter.new(sections, - self_link: api_v3_paths.meeting_sections(@meeting.id), + self_link: api_v3_paths.meeting_sections(meeting_id: @meeting.id), current_user:) end - post(&::API::V3::Utilities::Endpoints::Create - .new(model: MeetingSection, - params_modifier: ->(params) { - params.except(:meeting, :meeting_id).merge(meeting: @meeting) - }) - .mount) - route_param :section_id, type: Integer, desc: "Section ID" do after_validation do @meeting_section = @meeting.sections.find(declared_params[:section_id]) end get &::API::V3::Utilities::Endpoints::Show.new(model: MeetingSection).mount - - patch &::API::V3::Utilities::Endpoints::Update.new(model: MeetingSection).mount - - delete &::API::V3::Utilities::Endpoints::Delete.new(model: MeetingSection).mount end end end diff --git a/modules/meeting/lib/api/v3/meetings/meeting_representer.rb b/modules/meeting/lib/api/v3/meetings/meeting_representer.rb index 11cc02bb724..793490a5a01 100644 --- a/modules/meeting/lib/api/v3/meetings/meeting_representer.rb +++ b/modules/meeting/lib/api/v3/meetings/meeting_representer.rb @@ -76,13 +76,13 @@ module API link :agendaItems do { - href: api_v3_paths.meeting_agenda_items(represented.id) + href: api_v3_paths.meeting_agenda_items(meeting_id: represented.id) } end link :sections do { - href: api_v3_paths.meeting_sections(represented.id) + href: api_v3_paths.meeting_sections(meeting_id: represented.id) } end diff --git a/modules/meeting/lib/api/v3/meetings/meetings_api.rb b/modules/meeting/lib/api/v3/meetings/meetings_api.rb index 34e793394d3..d339b4cc684 100644 --- a/modules/meeting/lib/api/v3/meetings/meetings_api.rb +++ b/modules/meeting/lib/api/v3/meetings/meetings_api.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + #-- copyright # OpenProject is an open source project management software. # Copyright (C) the OpenProject GmbH @@ -58,6 +59,10 @@ module API mount ::API::V3::MeetingSections::SectionsByMeetingAPI end end + + mount ::API::V3::MeetingAgendaItems::MeetingAgendaItemsAPI + mount ::API::V3::MeetingSections::MeetingSectionsAPI + mount ::API::V3::MeetingOutcomes::MeetingOutcomesAPI end end end diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index c7894770d41..8ea3f1a9cdc 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -241,28 +241,52 @@ module OpenProject::Meeting "#{root}/meetings/#{id}/form" end - add_api_path :meeting_agenda_items do |meeting_id| - "#{meeting(meeting_id)}/agenda_items" + add_api_path :meeting_agenda_items do |meeting_id: nil| + if meeting_id + "#{meeting(meeting_id)}/agenda_items" + else + "#{root}/meeting_agenda_items" + end end - add_api_path :meeting_agenda_item do |meeting_id, id| - "#{meeting(meeting_id)}/agenda_items/#{id}" + add_api_path :meeting_agenda_item do |id, meeting_id: nil| + if meeting_id + "#{meeting(meeting_id)}/agenda_items/#{id}" + else + "#{root}/meeting_agenda_items/#{id}" + end end - add_api_path :meeting_agenda_item_outcomes do |meeting_id, agenda_item_id| - "#{meeting_agenda_item(meeting_id, agenda_item_id)}/outcomes" + add_api_path :meeting_agenda_item_outcomes do |agenda_item_id, meeting_id: nil| + "#{meeting_agenda_item(agenda_item_id, meeting_id:)}/outcomes" end - add_api_path :meeting_agenda_item_outcome do |meeting_id, agenda_item_id, id| - "#{meeting_agenda_item_outcomes(meeting_id, agenda_item_id)}/#{id}" + add_api_path :meeting_outcomes do + "#{root}/meeting_outcomes" end - add_api_path :meeting_sections do |meeting_id| - "#{meeting(meeting_id)}/sections" + add_api_path :meeting_outcome do |id| + "#{root}/meeting_outcomes/#{id}" end - add_api_path :meeting_section do |meeting_id, id| - "#{meeting(meeting_id)}/sections/#{id}" + add_api_path :meeting_agenda_item_outcome do |id, agenda_item_id:, meeting_id: nil| + "#{meeting_agenda_item_outcomes(agenda_item_id, meeting_id:)}/#{id}" + end + + add_api_path :meeting_sections do |meeting_id: nil| + if meeting_id + "#{meeting(meeting_id)}/sections" + else + "#{root}/meeting_sections" + end + end + + add_api_path :meeting_section do |id, meeting_id: nil| + if meeting_id + "#{meeting(meeting_id)}/sections/#{id}" + else + "#{root}/meeting_sections/#{id}" + end end add_api_path :recurring_meetings do diff --git a/modules/meeting/spec/requests/api/v3/meeting_agenda_items/agenda_items_by_meeting_resource_spec.rb b/modules/meeting/spec/requests/api/v3/meeting_agenda_items/agenda_items_by_meeting_resource_spec.rb index 5d5bdb39574..c040a31a1ed 100644 --- a/modules/meeting/spec/requests/api/v3/meeting_agenda_items/agenda_items_by_meeting_resource_spec.rb +++ b/modules/meeting/spec/requests/api/v3/meeting_agenda_items/agenda_items_by_meeting_resource_spec.rb @@ -51,7 +51,7 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d end describe "GET /api/v3/meetings/:meeting_id/agenda_items" do - let(:path) { api_v3_paths.meeting_agenda_items(meeting.id) } + let(:path) { api_v3_paths.meeting_agenda_items(meeting_id: meeting.id) } before { get path } @@ -69,6 +69,18 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d expect(last_response.body) .to have_json_size(1) .at_path("_embedded/elements/0/_embedded/outcomes") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_outcome(outcome.id).to_json) + .at_path("_embedded/elements/0/_links/outcomes/0/href") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_agenda_item(agenda_item.id).to_json) + .at_path("_embedded/elements/0/_links/self/href") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_section(section.id).to_json) + .at_path("_embedded/elements/0/_links/section/href") end context "without view_meetings permission" do @@ -80,11 +92,16 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d end end - describe "POST /api/v3/meetings/:meeting_id/agenda_items" do - let(:path) { api_v3_paths.meeting_agenda_items(meeting.id) } + describe "POST /api/v3/meeting_agenda_items" do + let(:path) { api_v3_paths.meeting_agenda_items } let(:body) do { - title: "New agenda item" + title: "New agenda item", + _links: { + meeting: { + href: api_v3_paths.meeting(meeting.id) + } + } }.to_json end @@ -109,6 +126,44 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d .at_path("title") end + context "with a section href returned by the section collection" do + let!(:target_section) { create(:meeting_section, meeting:) } + let(:target_section_href) do + get api_v3_paths.meeting_sections(meeting_id: meeting.id) + + JSON + .parse(last_response.body) + .dig("_embedded", "elements") + .find { |item| item["id"] == target_section.id } + .dig("_links", "self", "href") + end + let(:body) do + { + title: "New agenda item in target section", + _links: { + meeting: { + href: api_v3_paths.meeting(meeting.id) + }, + section: { + href: target_section_href + } + } + }.to_json + end + + it "responds with 201" do + expect(target_section_href).to eq(api_v3_paths.meeting_section(target_section.id)) + expect(response).to have_http_status(:created) + end + + it "creates the agenda item in that section" do + response + + expect(meeting.agenda_items.find_by(title: "New agenda item in target section").meeting_section) + .to eq(target_section) + end + end + context "without manage_agendas permission" do let(:permissions) { %i[view_meetings] } @@ -119,7 +174,7 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d end describe "GET /api/v3/meetings/:meeting_id/agenda_items/:id" do - let(:path) { api_v3_paths.meeting_agenda_item(meeting.id, agenda_item.id) } + let(:path) { api_v3_paths.meeting_agenda_item(agenda_item.id, meeting_id: meeting.id) } before { get path } @@ -141,7 +196,7 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d context "with an item from another meeting" do let(:other_meeting) { create(:meeting, project:, author: current_user) } - let(:path) { api_v3_paths.meeting_agenda_item(other_meeting.id, agenda_item.id) } + let(:path) { api_v3_paths.meeting_agenda_item(agenda_item.id, meeting_id: other_meeting.id) } it "returns 404" do expect(last_response).to have_http_status(:not_found) @@ -155,7 +210,7 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d create(:wp_meeting_agenda_item, meeting:, meeting_section: section, work_package: private_work_package, author: current_user) end - let(:path) { api_v3_paths.meeting_agenda_item(meeting.id, wp_agenda_item.id) } + let(:path) { api_v3_paths.meeting_agenda_item(wp_agenda_item.id, meeting_id: meeting.id) } it "returns 200" do expect(last_response).to have_http_status(:ok) @@ -173,8 +228,34 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d end end - describe "PATCH /api/v3/meetings/:meeting_id/agenda_items/:id" do - let(:path) { api_v3_paths.meeting_agenda_item(meeting.id, agenda_item.id) } + describe "GET /api/v3/meeting_agenda_items/:id" do + let(:path) { api_v3_paths.meeting_agenda_item(agenda_item.id) } + + before { get path } + + it "returns 200 and the agenda item" do + expect(last_response).to have_http_status(:ok) + + expect(last_response.body) + .to be_json_eql("MeetingAgendaItem".to_json) + .at_path("_type") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_agenda_item(agenda_item.id).to_json) + .at_path("_links/self/href") + end + + context "without view_meetings permission" do + let(:permissions) { [] } + + it "returns 404" do + expect(last_response).to have_http_status(:not_found) + end + end + end + + describe "PATCH /api/v3/meeting_agenda_items/:id" do + let(:path) { api_v3_paths.meeting_agenda_item(agenda_item.id) } let(:body) do { title: "Updated title", @@ -193,6 +274,42 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d expect(agenda_item.reload.title).to eq("Updated title") end + context "with a section href returned by the agenda item collection" do + let(:target_section) { create(:meeting_section, meeting:) } + let!(:target_agenda_item) do + create(:meeting_agenda_item, meeting:, meeting_section: target_section, author: current_user) + end + let(:target_section_href) do + get api_v3_paths.meeting_agenda_items(meeting_id: meeting.id) + + JSON + .parse(last_response.body) + .dig("_embedded", "elements") + .find { |item| item["id"] == target_agenda_item.id } + .dig("_links", "section", "href") + end + let(:body) do + { + lockVersion: agenda_item.lock_version, + _links: { + section: { + href: target_section_href + } + } + }.to_json + end + + it "responds with 200" do + expect(target_section_href).to eq(api_v3_paths.meeting_section(target_section.id)) + expect(response).to have_http_status(:ok) + end + + it "moves the agenda item to that section" do + response + expect(agenda_item.reload.meeting_section).to eq(target_section) + end + end + context "without manage_agendas permission" do let(:permissions) { %i[view_meetings] } @@ -202,8 +319,8 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d end end - describe "DELETE /api/v3/meetings/:meeting_id/agenda_items/:id" do - let(:path) { api_v3_paths.meeting_agenda_item(meeting.id, agenda_item.id) } + describe "DELETE /api/v3/meeting_agenda_items/:id" do + let(:path) { api_v3_paths.meeting_agenda_item(agenda_item.id) } before { delete path } diff --git a/modules/meeting/spec/requests/api/v3/meeting_outcomes/outcomes_by_agenda_item_resource_spec.rb b/modules/meeting/spec/requests/api/v3/meeting_outcomes/outcomes_by_agenda_item_resource_spec.rb index dc0b3d6419a..5dfc4be0954 100644 --- a/modules/meeting/spec/requests/api/v3/meeting_outcomes/outcomes_by_agenda_item_resource_spec.rb +++ b/modules/meeting/spec/requests/api/v3/meeting_outcomes/outcomes_by_agenda_item_resource_spec.rb @@ -51,7 +51,7 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do end describe "GET /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id/outcomes" do - let(:path) { api_v3_paths.meeting_agenda_item_outcomes(meeting.id, agenda_item.id) } + let(:path) { api_v3_paths.meeting_agenda_item_outcomes(agenda_item.id, meeting_id: meeting.id) } before { get path } @@ -65,11 +65,19 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do expect(last_response.body) .to have_json_size(1) .at_path("_embedded/elements") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_outcome(outcome.id).to_json) + .at_path("_embedded/elements/0/_links/self/href") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_agenda_item(agenda_item.id).to_json) + .at_path("_embedded/elements/0/_links/agendaItem/href") end context "with an agenda item from another meeting" do let(:other_meeting) { create(:meeting, project:, author: current_user) } - let(:path) { api_v3_paths.meeting_agenda_item_outcomes(other_meeting.id, agenda_item.id) } + let(:path) { api_v3_paths.meeting_agenda_item_outcomes(agenda_item.id, meeting_id: other_meeting.id) } it "returns 404" do expect(last_response).to have_http_status(:not_found) @@ -108,12 +116,17 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do end end - describe "POST /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id/outcomes" do - let(:path) { api_v3_paths.meeting_agenda_item_outcomes(meeting.id, agenda_item.id) } + describe "POST /api/v3/meeting_outcomes" do + let(:path) { api_v3_paths.meeting_outcomes } let(:body) do { kind: "information", - notes: { raw: "Outcome created via API" } + notes: { raw: "Outcome created via API" }, + _links: { + agendaItem: { + href: api_v3_paths.meeting_agenda_item(agenda_item.id) + } + } }.to_json end @@ -153,6 +166,9 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do { kind: "work_package", _links: { + agendaItem: { + href: api_v3_paths.meeting_agenda_item(agenda_item.id) + }, workPackage: { href: api_v3_paths.work_package(work_package.id) } @@ -182,6 +198,9 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do { kind: "work_package", _links: { + agendaItem: { + href: api_v3_paths.meeting_agenda_item(agenda_item.id) + }, workPackage: { href: api_v3_paths.work_package(private_work_package.id) } @@ -197,7 +216,11 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do end describe "GET /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id/outcomes/:id" do - let(:path) { api_v3_paths.meeting_agenda_item_outcome(meeting.id, agenda_item.id, outcome.id) } + let(:path) do + api_v3_paths.meeting_agenda_item_outcome(outcome.id, + agenda_item_id: agenda_item.id, + meeting_id: meeting.id) + end before { get path } @@ -211,11 +234,19 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do expect(last_response.body) .to be_json_eql(outcome.id.to_json) .at_path("id") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_outcome(outcome.id).to_json) + .at_path("_links/self/href") end context "with an outcome from another agenda item" do let(:other_agenda_item) { create(:meeting_agenda_item, meeting:, meeting_section: section, author: current_user) } - let(:path) { api_v3_paths.meeting_agenda_item_outcome(meeting.id, other_agenda_item.id, outcome.id) } + let(:path) do + api_v3_paths.meeting_agenda_item_outcome(outcome.id, + agenda_item_id: other_agenda_item.id, + meeting_id: meeting.id) + end it "returns 404" do expect(last_response).to have_http_status(:not_found) @@ -242,13 +273,38 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do .at_path("_links/workPackage/href") expect(last_response.body).not_to have_json_path("_embedded/workPackage") - end end end - describe "PATCH /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id/outcomes/:id" do - let(:path) { api_v3_paths.meeting_agenda_item_outcome(meeting.id, agenda_item.id, outcome.id) } + describe "GET /api/v3/meeting_outcomes/:id" do + let(:path) { api_v3_paths.meeting_outcome(outcome.id) } + + before { get path } + + it "returns 200 and the outcome" do + expect(last_response).to have_http_status(:ok) + + expect(last_response.body) + .to be_json_eql("MeetingOutcome".to_json) + .at_path("_type") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_outcome(outcome.id).to_json) + .at_path("_links/self/href") + end + + context "without view_meetings permission" do + let(:permissions) { [] } + + it "returns 404" do + expect(last_response).to have_http_status(:not_found) + end + end + end + + describe "PATCH /api/v3/meeting_outcomes/:id" do + let(:path) { api_v3_paths.meeting_outcome(outcome.id) } let(:body) do { notes: { raw: "Updated outcome" } @@ -271,8 +327,8 @@ RSpec.describe "API v3 Meeting Outcomes sub-resource", content_type: :json do end end - describe "DELETE /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id/outcomes/:id" do - let(:path) { api_v3_paths.meeting_agenda_item_outcome(meeting.id, agenda_item.id, outcome.id) } + describe "DELETE /api/v3/meeting_outcomes/:id" do + let(:path) { api_v3_paths.meeting_outcome(outcome.id) } before { delete path } diff --git a/modules/meeting/spec/requests/api/v3/meeting_sections/sections_by_meeting_resource_spec.rb b/modules/meeting/spec/requests/api/v3/meeting_sections/sections_by_meeting_resource_spec.rb index 17782fcc872..ebfd2190f65 100644 --- a/modules/meeting/spec/requests/api/v3/meeting_sections/sections_by_meeting_resource_spec.rb +++ b/modules/meeting/spec/requests/api/v3/meeting_sections/sections_by_meeting_resource_spec.rb @@ -49,7 +49,7 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do end describe "GET /api/v3/meetings/:meeting_id/sections" do - let(:path) { api_v3_paths.meeting_sections(meeting.id) } + let(:path) { api_v3_paths.meeting_sections(meeting_id: meeting.id) } before { get path } @@ -59,6 +59,10 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do expect(last_response.body) .to be_json_eql("Collection".to_json) .at_path("_type") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_section(section.id).to_json) + .at_path("_embedded/elements/0/_links/self/href") end context "without view_meetings permission" do @@ -70,11 +74,16 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do end end - describe "POST /api/v3/meetings/:meeting_id/sections" do - let(:path) { api_v3_paths.meeting_sections(meeting.id) } + describe "POST /api/v3/meeting_sections" do + let(:path) { api_v3_paths.meeting_sections } let(:body) do { - title: "New Section" + title: "New Section", + _links: { + meeting: { + href: api_v3_paths.meeting(meeting.id) + } + } }.to_json end @@ -109,7 +118,7 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do end describe "GET /api/v3/meetings/:meeting_id/sections/:id" do - let(:path) { api_v3_paths.meeting_section(meeting.id, section.id) } + let(:path) { api_v3_paths.meeting_section(section.id, meeting_id: meeting.id) } before { get path } @@ -127,7 +136,7 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do context "with a section from another meeting" do let(:other_meeting) { create(:meeting, project:, author: current_user) } - let(:path) { api_v3_paths.meeting_section(other_meeting.id, section.id) } + let(:path) { api_v3_paths.meeting_section(section.id, meeting_id: other_meeting.id) } it "returns 404" do expect(last_response).to have_http_status(:not_found) @@ -135,8 +144,34 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do end end - describe "PATCH /api/v3/meetings/:meeting_id/sections/:id" do - let(:path) { api_v3_paths.meeting_section(meeting.id, section.id) } + describe "GET /api/v3/meeting_sections/:id" do + let(:path) { api_v3_paths.meeting_section(section.id) } + + before { get path } + + it "returns 200 and the section" do + expect(last_response).to have_http_status(:ok) + + expect(last_response.body) + .to be_json_eql("MeetingSection".to_json) + .at_path("_type") + + expect(last_response.body) + .to be_json_eql(api_v3_paths.meeting_section(section.id).to_json) + .at_path("_links/self/href") + end + + context "without view_meetings permission" do + let(:permissions) { [] } + + it "returns 404" do + expect(last_response).to have_http_status(:not_found) + end + end + end + + describe "PATCH /api/v3/meeting_sections/:id" do + let(:path) { api_v3_paths.meeting_section(section.id) } let(:body) do { title: "Updated Section Title" @@ -163,8 +198,8 @@ RSpec.describe "API v3 Meeting Sections sub-resource", content_type: :json do end end - describe "DELETE /api/v3/meetings/:meeting_id/sections/:id" do - let(:path) { api_v3_paths.meeting_section(meeting.id, section.id) } + describe "DELETE /api/v3/meeting_sections/:id" do + let(:path) { api_v3_paths.meeting_section(section.id) } before { delete path }