maintain paths nested under /api/v3/projects/:id for all workspaces

This commit is contained in:
ulferts
2025-11-26 09:56:13 +01:00
parent 5fd500ff6a
commit 40ff3deda1
10 changed files with 134 additions and 54 deletions
+6 -2
View File
@@ -57,10 +57,14 @@ module API
end
route_param :id do
after_validation do
# TODO: This should be scoped to only allow actual projects.
# But since it and especially the NestedAPIs are established routes,
# we keep it intact for all kinds of workspaces for 17.0.
# This behaviour is not documented in the API docs to nudge users to switch.
@project = if current_user.admin?
Project.project
Project
else
Project.project.visible(current_user)
Project.visible(current_user)
end.find(params[:id])
end
@@ -34,17 +34,19 @@ require_relative "../workspaces/delete_resource_examples"
RSpec.describe "API v3 Project resource delete", content_type: :json do
describe "DELETE /api/v3/projects/:id" do
include_examples "APIv3 deleting a workspace" do
let(:workspace) { create(:project) }
let(:path_id) { workspace.id }
let(:path) { api_v3_paths.project(path_id) }
context "for a project" do
include_examples "APIv3 deleting a workspace" do
let(:workspace) { create(:project) }
let(:path_id) { workspace.id }
let(:path) { api_v3_paths.project(path_id) }
end
end
context "with a portfolio id" do
let(:workspace) do
create(:portfolio, public: true)
end
it_behaves_like "not found"
context "for a portfolio" do
include_examples "APIv3 deleting a workspace" do
let(:workspace) { create(:portfolio) }
let(:path_id) { workspace.id }
let(:path) { api_v3_paths.project(path_id) }
end
end
end
@@ -189,7 +189,14 @@ RSpec.describe "API v3 Project resource show", content_type: :json do
response
end
it_behaves_like "not found"
it "responds with 200 OK" do
expect(subject.status).to eq(200)
end
it "responds with the correct project" do
expect(subject.body).to include_json("Portfolio".to_json).at_path("_type")
expect(subject.body).to be_json_eql(project.identifier.to_json).at_path("identifier")
end
end
context "when not being allowed to see the parent project" do
@@ -34,16 +34,21 @@ require_relative "../workspaces/update_form_resource_examples"
RSpec.describe "API v3 Project resource update form", content_type: :json do
describe "POST /api/v3/projects/:id/form" do
include_examples "APIv3 workspace update form" do
shared_let(:workspace, reload: true) { create(:project) }
context "for a project" do
include_examples "APIv3 workspace update form" do
shared_let(:workspace, reload: true) { create(:project) }
let(:path) { api_v3_paths.project_form(path_id) }
let(:workspace_path) { api_v3_paths.project(workspace.id) }
let(:path) { api_v3_paths.project_form(path_id) }
let(:workspace_path) { api_v3_paths.project(workspace.id) }
end
end
context "with a portfolio id" do
let(:workspace) { create(:portfolio, public: true) }
context "for a portfolio" do
include_examples "APIv3 workspace update form" do
shared_let(:workspace, reload: true) { create(:portfolio) }
it_behaves_like "not found"
let(:path) { api_v3_paths.project_form(path_id) }
let(:workspace_path) { api_v3_paths.portfolio(workspace.id) }
end
end
end
@@ -34,17 +34,19 @@ require_relative "../workspaces/update_resource_examples"
RSpec.describe "API v3 Project resource update", content_type: :json do
describe "PATCH /api/v3/projects/:id" do
include_examples "APIv3 workspace update" do
let(:path) { api_v3_paths.project(workspace.id) }
let(:workspace_factory_key) { :project }
let(:workspace_api_type) { "Project" }
context "for a project" do
include_examples "APIv3 workspace update" do
let(:path) { api_v3_paths.project(workspace.id) }
let(:workspace_factory_key) { :project }
let(:workspace_api_type) { "Project" }
end
end
context "with a portfolio id" do
let(:workspace) do
create(:portfolio, public: true)
end
it_behaves_like "not found"
context "for a portfolio" do
include_examples "APIv3 workspace update" do
let(:path) { api_v3_paths.project(workspace.id) }
let(:workspace_factory_key) { :portfolio }
let(:workspace_api_type) { "Portfolio" }
end
end
end
@@ -35,7 +35,8 @@ RSpec.describe "GET workspaces/:id/queries/default" do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:project) { create(:project) }
shared_let(:project_instance) { create(:project) }
shared_let(:portfolio_instance) { create(:portfolio) }
let(:role) { create(:project_role, permissions:) }
let(:permissions) { [:view_work_packages] }
@@ -47,19 +48,40 @@ RSpec.describe "GET workspaces/:id/queries/default" do
end
context "for a project scope" do
it_behaves_like "GET individual query" do
let(:base_path) { api_v3_paths.query_project_default(project.id) }
let(:self_path) { api_v3_paths.query_workspace_default(project.id) }
context "for a project" do
let(:project) { project_instance }
context "when lacking permissions" do
let(:permissions) { [] }
it_behaves_like "GET individual query" do
let(:base_path) { api_v3_paths.query_project_default(project.id) }
let(:self_path) { api_v3_paths.query_workspace_default(project.id) }
it_behaves_like "unauthorized access"
context "when lacking permissions" do
let(:permissions) { [] }
it_behaves_like "unauthorized access"
end
end
end
context "for a portfolio" do
let(:project) { portfolio_instance }
it_behaves_like "GET individual query" do
let(:base_path) { api_v3_paths.query_project_default(project.id) }
let(:self_path) { api_v3_paths.query_workspace_default(project.id) }
context "when lacking permissions" do
let(:permissions) { [] }
it_behaves_like "unauthorized access"
end
end
end
end
context "for a workspace scope" do
let(:project) { project_instance }
it_behaves_like "GET individual query" do
let(:base_path) { api_v3_paths.query_workspace_default(project.id) }
context "when lacking permissions" do
@@ -78,14 +78,23 @@ RSpec.describe "/api/v3/projects/:id/types" do
end
end
context "for a project" do
let(:project) { create(:project, no_types: true) }
let(:get_path) { api_v3_paths.types_by_project requested_project.id }
context "when using the projects route" do
context "for a project" do
let(:project) { create(:project, no_types: true) }
let(:get_path) { api_v3_paths.types_by_project requested_project.id }
include_context "for types by workspace"
include_context "for types by workspace"
end
context "for a portfolio" do
let(:project) { create(:portfolio, no_types: true) }
let(:get_path) { api_v3_paths.types_by_project requested_project.id }
include_context "for types by workspace"
end
end
context "for a workspace" do
context "when using the workspaces route" do
let(:project) { create(:portfolio, no_types: true) }
let(:get_path) { api_v3_paths.types_by_workspace requested_project.id }
@@ -191,10 +191,19 @@ RSpec.describe "POST api/v3/workspace/:id/work_packages", content_type: :json do
end
context "for a project path" do
let(:path) { api_v3_paths.work_packages_by_project project.id }
let(:workspace) { project }
context "within a project" do
let(:path) { api_v3_paths.work_packages_by_project project.id }
let(:workspace) { project }
include_context "with work package creation"
include_context "with work package creation"
end
context "within a portfolio" do
let(:path) { api_v3_paths.work_packages_by_project workspace.id }
let(:workspace) { portfolio }
include_context "with work package creation"
end
end
context "for a workspace path" do
@@ -315,10 +315,19 @@ RSpec.describe "GET api/v3/workspace/:id/work_packages", content_type: :json do
end
context "for a project path" do
let(:base_path) { api_v3_paths.work_packages_by_project project.id }
let(:workspace) { project }
context "within a project" do
let(:base_path) { api_v3_paths.work_packages_by_project project.id }
let(:workspace) { project }
include_context "with work package indexing"
include_context "with work package indexing"
end
context "within a portfolio" do
let(:base_path) { api_v3_paths.work_packages_by_project portfolio.id }
let(:workspace) { portfolio }
include_context "with work package indexing"
end
end
context "for a workspace path" do
@@ -33,13 +33,24 @@ require "spec_helper"
RSpec.describe "API::V3::Workspaces::AvailableAssigneesAPI" do
include API::V3::Utilities::PathHelper
it_behaves_like "available principals", :assignees do
let(:base_permissions) { %i[add_work_packages] }
let(:href) { api_v3_paths.available_assignees_in_workspace(project.id) }
context "when using the workspaces route" do
it_behaves_like "available principals", :assignees do
let(:base_permissions) { %i[add_work_packages] }
let(:href) { api_v3_paths.available_assignees_in_workspace(project.id) }
end
end
it_behaves_like "available principals", :assignees do
let(:base_permissions) { %i[add_work_packages] }
let(:href) { api_v3_paths.available_assignees_in_project(project.id) }
context "when using the projects route" do
it_behaves_like "available principals", :assignees do
let(:base_permissions) { %i[add_work_packages] }
let(:href) { api_v3_paths.available_assignees_in_project(project.id) }
end
context "for a non project" do
it_behaves_like "available principals", :assignees do
let(:base_permissions) { %i[add_work_packages] }
let(:project) { create(:portfolio) }
let(:href) { api_v3_paths.available_assignees_in_project(project.id) }
end
end
end
end