From 40ff3deda16bacb16265bafad8121d32098a7bbf Mon Sep 17 00:00:00 2001 From: ulferts Date: Wed, 26 Nov 2025 09:56:13 +0100 Subject: [PATCH] maintain paths nested under /api/v3/projects/:id for all workspaces --- lib/api/v3/projects/projects_api.rb | 8 +++-- .../api/v3/projects/delete_resource_spec.rb | 22 ++++++------ .../api/v3/projects/show_resource_spec.rb | 9 ++++- .../v3/projects/update_form_resource_spec.rb | 19 ++++++---- .../api/v3/projects/update_resource_spec.rb | 22 ++++++------ .../queries_by_workspace_resource_spec.rb | 36 +++++++++++++++---- .../types/types_by_workspace_resource_spec.rb | 19 +++++++--- .../by_workspace_create_resource_spec.rb | 15 ++++++-- .../by_workspace_index_resource_spec.rb | 15 ++++++-- .../available_assignees_api_spec.rb | 23 ++++++++---- 10 files changed, 134 insertions(+), 54 deletions(-) diff --git a/lib/api/v3/projects/projects_api.rb b/lib/api/v3/projects/projects_api.rb index d4787c535a6..87a830411de 100644 --- a/lib/api/v3/projects/projects_api.rb +++ b/lib/api/v3/projects/projects_api.rb @@ -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 diff --git a/spec/requests/api/v3/projects/delete_resource_spec.rb b/spec/requests/api/v3/projects/delete_resource_spec.rb index a5b9555a523..45e64e9c921 100644 --- a/spec/requests/api/v3/projects/delete_resource_spec.rb +++ b/spec/requests/api/v3/projects/delete_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/projects/show_resource_spec.rb b/spec/requests/api/v3/projects/show_resource_spec.rb index 132c6fadc43..16503d1af97 100644 --- a/spec/requests/api/v3/projects/show_resource_spec.rb +++ b/spec/requests/api/v3/projects/show_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/projects/update_form_resource_spec.rb b/spec/requests/api/v3/projects/update_form_resource_spec.rb index 1ae312ad31a..5bc09c83047 100644 --- a/spec/requests/api/v3/projects/update_form_resource_spec.rb +++ b/spec/requests/api/v3/projects/update_form_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/projects/update_resource_spec.rb b/spec/requests/api/v3/projects/update_resource_spec.rb index 1fa99801453..a183ab8fa6b 100644 --- a/spec/requests/api/v3/projects/update_resource_spec.rb +++ b/spec/requests/api/v3/projects/update_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/queries/queries_by_workspace_resource_spec.rb b/spec/requests/api/v3/queries/queries_by_workspace_resource_spec.rb index c972088cb8b..4b7e1221ca6 100644 --- a/spec/requests/api/v3/queries/queries_by_workspace_resource_spec.rb +++ b/spec/requests/api/v3/queries/queries_by_workspace_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/types/types_by_workspace_resource_spec.rb b/spec/requests/api/v3/types/types_by_workspace_resource_spec.rb index fa6ace075ff..5cdb7c46cac 100644 --- a/spec/requests/api/v3/types/types_by_workspace_resource_spec.rb +++ b/spec/requests/api/v3/types/types_by_workspace_resource_spec.rb @@ -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 } diff --git a/spec/requests/api/v3/work_packages/by_workspace_create_resource_spec.rb b/spec/requests/api/v3/work_packages/by_workspace_create_resource_spec.rb index 5d035f231e3..264a26f84d2 100644 --- a/spec/requests/api/v3/work_packages/by_workspace_create_resource_spec.rb +++ b/spec/requests/api/v3/work_packages/by_workspace_create_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/work_packages/by_workspace_index_resource_spec.rb b/spec/requests/api/v3/work_packages/by_workspace_index_resource_spec.rb index d0b1ad8938c..b43d8ab65b0 100644 --- a/spec/requests/api/v3/work_packages/by_workspace_index_resource_spec.rb +++ b/spec/requests/api/v3/work_packages/by_workspace_index_resource_spec.rb @@ -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 diff --git a/spec/requests/api/v3/workspaces/available_assignees_api_spec.rb b/spec/requests/api/v3/workspaces/available_assignees_api_spec.rb index 9b3d5d36f13..d3c92404c7b 100644 --- a/spec/requests/api/v3/workspaces/available_assignees_api_spec.rb +++ b/spec/requests/api/v3/workspaces/available_assignees_api_spec.rb @@ -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