From 0b729e8d76578d548c8c935184578244cbd276d5 Mon Sep 17 00:00:00 2001
From: Jarvis-J-Jacob <287212888+Jarvis-J-Jacob@users.noreply.github.com>
Date: Wed, 2 Sep 2026 20:30:32 +0530
Subject: [PATCH] Fix 500 in public#binary_packages for scmsync project
packages
Packages of scmsync projects live in the project's git repository, not in
the database. #binary_packages guarded access with check_package_access
(use_source: false) and then looked @pkg up with a plain DB query, which
returns nil for a package of an scmsync project. @pkg stayed nil and the
action crashed at @pkg.project.repositories with "undefined method
'project' for nil" -> HTTP 500.
Look @pkg up with Package.get_by_project_and_name(use_source: false,
follow_project_scmsync_links: true) instead. It resolves the same database
packages the previous lookup did, and additionally builds a read-only
Package from the backend package meta for scmsync projects, providing the
project, its repositories and the name/title/description the action and
its view need.
Side effect: a request for a package that does not exist on the backend
now returns 404 (Package::UnknownObjectError) instead of 500. Non-scmsync
projects are unaffected: a normal missing package already raised in
check_package_access before this line.
Fixes #20224
---
src/api/app/controllers/public_controller.rb | 6 ++-
.../controllers/public_controller_spec.rb | 45 +++++++++++++++++++
2 files changed, 50 insertions(+), 1 deletion(-)
diff --git a/src/api/app/controllers/public_controller.rb b/src/api/app/controllers/public_controller.rb
index 1ecafc2a457..2468a6aaff7 100644
--- a/src/api/app/controllers/public_controller.rb
+++ b/src/api/app/controllers/public_controller.rb
@@ -145,7 +145,11 @@ def show_request
# GET /public/binary_packages/:project/:package
def binary_packages
check_package_access(params[:project], params[:package], use_source: false)
- @pkg = Package.find_by_project_and_name(params[:project], params[:package])
+ # Packages of scmsync projects don't exist in the database. follow_project_scmsync_links
+ # builds a read-only instance from the backend package meta so we can still resolve the
+ # project, its repositories and the package name/title/description below.
+ @pkg = Package.get_by_project_and_name(params[:project], params[:package],
+ use_source: false, follow_project_scmsync_links: true)
begin
binaries = Xmlhash.parse(Backend::Api::Search.published_binaries_for_package(params[:project], params[:package]))
diff --git a/src/api/spec/controllers/public_controller_spec.rb b/src/api/spec/controllers/public_controller_spec.rb
index dcda24c8b68..fcfb998ad1d 100644
--- a/src/api/spec/controllers/public_controller_spec.rb
+++ b/src/api/spec/controllers/public_controller_spec.rb
@@ -189,4 +189,49 @@
it { expect(revisions.count).to eq(1) }
end
end
+
+ # NOTE: defined last on purpose. VCR names cassettes for anonymous `it { ... }`
+ # examples after their scoped id, so inserting an example group earlier in this
+ # file would renumber the recorded cassettes of the groups that follow it.
+ describe 'GET #binary_packages for a package of an scmsync project' do
+ let(:scmsync_project) do
+ create(:project, name: 'scmsync_public_controller_project', scmsync: 'https://github.com/example/scmsync-project.git')
+ end
+ let(:package_meta) do
+ <<~XML
+
+ eza title
+ eza description
+
+ XML
+ end
+
+ before do
+ allow(Backend::Api::Search).to receive(:published_binaries_for_package).and_return('')
+ end
+
+ context 'when the package exists on the backend' do
+ before do
+ # scmsync packages don't exist in the database, they are served from the backend
+ allow(Backend::Api::Sources::Package).to receive(:meta).and_return(package_meta)
+
+ get :binary_packages, params: { project: scmsync_project.name, package: 'eza' }
+ end
+
+ it { expect(response).to have_http_status(:success) }
+ it { expect(assigns(:pkg)).to be_a(Package) }
+ it { expect(assigns(:pkg).name).to eq('eza') }
+ it { expect(assigns(:pkg).project).to eq(scmsync_project) }
+ end
+
+ context 'when the package does not exist on the backend' do
+ before do
+ allow(Backend::Api::Sources::Package).to receive(:meta).and_raise(Backend::NotFoundError)
+
+ get :binary_packages, params: { project: scmsync_project.name, package: 'does_not_exist' }
+ end
+
+ it { expect(response).to have_http_status(:not_found) }
+ end
+ end
end