Add flat paths for meeting API

This commit is contained in:
Oliver Günther
2026-06-03 14:45:52 +02:00
parent 5bc54dd1ec
commit 164e31c1d5
15 changed files with 471 additions and 103 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 }
@@ -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 }
@@ -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 }