Drop numeric pins on auxilary links

This commit is contained in:
Kabiru Mwenja
2026-04-30 09:01:11 +03:00
parent 131f595ea4
commit 4dfdd6ec5d
5 changed files with 37 additions and 27 deletions
@@ -106,7 +106,7 @@ module API
next if represented.new_record?
{
href: new_work_package_move_path(work_package_id: represented.id),
href: new_work_package_move_path(represented),
type: "text/html",
title: "Move work package '#{represented.subject}'"
}
@@ -117,7 +117,7 @@ module API
next if represented.new_record?
{
href: copy_work_package_path(id: represented.id),
href: work_package_path(represented, "copy"),
type: "text/html",
title: "Copy work package '#{represented.subject}'"
}
@@ -100,18 +100,21 @@ RSpec.shared_examples_for "provides a single WP context menu" do
context "with semantic identifiers enabled",
with_flag: { semantic_work_package_ids: true },
with_settings: { work_packages_identifier: "semantic" } do
# The shared project is created classic-style (lowercase identifier), but
# semantic mode requires the uppercase format. Rewrite it before any
# example-scoped setup so work-package allocation and URL helpers both see
# a valid semantic prefix. Specs that create their own project per example
# should prefer the `:semantic` factory trait instead.
around do |example|
semantic_project_identifier = "PROJ#{project.id}".first(Projects::Identifier::SEMANTIC_IDENTIFIER_MAX_LENGTH)
project.update_columns(identifier: semantic_project_identifier)
example.run
end
it "uses numeric parent_id in the URL and sets the parent correctly" do
expect(Setting::WorkPackageIdentifier.semantic_mode_active?)
.to be(true), "expected semantic mode to be active via with_settings + with_flag metadata"
# Consumers of this shared example use shared_let(:project), which evaluates
# before per-example `with_settings:` activates semantic mode — so the project
# was created via the classic-mode factory path with a lowercase slug. Force
# an identifier that satisfies Projects::Identifier's semantic constraints
# (uppercase, [A-Z][A-Z0-9_]*, max 10 chars). For specs that create their own
# project per example, prefer the `:semantic` factory trait instead.
semantic_project_identifier = "PROJ#{work_package.project.id}".first(Projects::Identifier::SEMANTIC_IDENTIFIER_MAX_LENGTH)
work_package.project.update_columns(identifier: semantic_project_identifier)
work_package.allocate_and_register_semantic_id if work_package.identifier.blank?
open_context_menu.call
@@ -1204,7 +1204,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do
it_behaves_like "has a titled action link" do
let(:link) { "move" }
let(:href) { "/work_packages/#{work_package.id}/move/new" }
let(:href) { "/work_packages/#{work_package.display_id}/move/new" }
let(:permission) { :move_work_packages }
let(:title) { "Move work package '#{work_package.subject}'" }
end
@@ -1214,7 +1214,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do
describe "copy" do
it_behaves_like "has a titled action link" do
let(:link) { "copy" }
let(:href) { "/work_packages/#{work_package.id}/copy" }
let(:href) { work_package_path(work_package, "copy") }
let(:permission) { :add_work_packages }
let(:title) { "Copy work package '#{work_package.subject}'" }
end
@@ -1226,7 +1226,7 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do
it_behaves_like "has a titled action link" do
let(:link) { "copy" }
let(:href) { "/work_packages/#{work_package.id}/copy" }
let(:href) { "/work_packages/#{work_package.display_id}/copy" }
let(:permission) { :add_work_packages }
let(:title) { "Copy work package '#{work_package.subject}'" }
end
@@ -1261,33 +1261,32 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do
end
end
# The HAL surface must produce numeric URLs regardless of identifier
# mode — external API consumers depend on stable, predictable IDs.
# These specs guard against a future change that drops the explicit
# `id:` kwargs (or model-positional pins) and silently lets URLs flip
# to semantic identifiers via WorkPackage#to_param.
# The HAL contract surface stays numeric in semantic mode so clients
# can correlate work packages by comparing href strings (one
# resource's `self` === another's `parent`, etc.). Auxiliary
# endpoints (`pdf`, `generate_pdf`, `atom`) follow the same convention.
context "with semantic identifier mode active",
with_flag: { semantic_work_package_ids: true },
with_settings: { work_packages_identifier: "semantic", feeds_enabled?: true } do
let(:work_package) { build_stubbed(:work_package, identifier: "PROJ-7", project: workspace) }
let(:permissions) { all_permissions + [:export_work_packages] }
it "self stays numeric" do
it "self href stays numeric" do
expect(subject).to be_json_eql("/api/v3/work_packages/#{work_package.id}".to_json)
.at_path("_links/self/href")
end
it "pdf stays numeric" do
it "pdf href stays numeric" do
expect(subject).to be_json_eql("/work_packages/#{work_package.id}.pdf".to_json)
.at_path("_links/pdf/href")
end
it "generate_pdf stays numeric" do
it "generate_pdf href stays numeric" do
expect(subject).to be_json_eql("/work_packages/#{work_package.id}/generate_pdf_dialog".to_json)
.at_path("_links/generate_pdf/href")
end
it "atom stays numeric" do
it "atom href stays numeric" do
expect(subject).to be_json_eql("/work_packages/#{work_package.id}.atom".to_json)
.at_path("_links/atom/href")
end
@@ -466,8 +466,9 @@ RSpec.describe WorkPackage::SemanticIdentifier do
end
end
context "when semantic mode is not active",
with_flag: { semantic_work_package_ids: false } do
context "when classic mode is active",
with_flag: { semantic_work_package_ids: false },
with_settings: { work_packages_identifier: "classic" } do
it "returns the numeric id as a string" do
expect(work_package.to_param).to eq(work_package.id.to_s)
end
@@ -475,10 +476,10 @@ RSpec.describe WorkPackage::SemanticIdentifier do
it "makes work_package_path produce a numeric URL" do
expect(work_package_path(work_package)).to end_with("/work_packages/#{work_package.id}")
end
end
it "returns nil for new (unsaved) records" do
expect(WorkPackage.new.to_param).to be_nil
it "returns nil for new (unsaved) records" do
expect(WorkPackage.new.to_param).to be_nil
end
end
end
@@ -221,6 +221,13 @@ RSpec.describe "API v3 Work package resource",
context "with a semantic identifier",
with_flag: { semantic_work_package_ids: true },
with_settings: { work_packages_identifier: "semantic" } do
let(:project) { create(:project, :semantic) }
let(:user) do
create(:user, member_with_permissions: { project => %i[view_work_packages] })
end
let(:work_package) do
create(:work_package, project:, description: "lorem ipsum")
end
let(:get_path) { api_v3_paths.work_package work_package.display_id }
before do