Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions app/controllers/api/v1/deletions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@ def validate_gem_and_version
begin
version = params.expect(:version)
platform = params.permit(:platform).fetch(:platform, nil)
@version = @rubygem.find_version!(number: version, platform: platform)
ruby_abi = params.permit(:ruby_abi).fetch(:ruby_abi, nil).presence

if ruby_abi.present? && platform.blank?
return render plain: response_with_mfa_warning("The platform param is required when ruby_abi is specified."),
status: :bad_request
end

@version = @rubygem.find_version!(number: version, platform: platform, ruby_abi: ruby_abi)
rescue ActiveRecord::RecordNotFound
render plain: response_with_mfa_warning("The version #{version}#{" (#{platform})" if platform.present?} does not exist."),
details = "#{" (#{platform})" if platform.present?}#{" (Ruby ABI #{ruby_abi})" if ruby_abi.present?}"
render plain: response_with_mfa_warning("The version #{version}#{details} does not exist."),
status: :not_found
end
end
Expand Down
4 changes: 2 additions & 2 deletions app/models/rubygem.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ def public_version_payload(number, platform = nil)
payload(version).merge!(version.as_json) if version
end

def find_version!(number:, platform:)
def find_version!(number:, platform:, ruby_abi: nil)
platform = platform.presence || "ruby"
versions.find_by!(number: number, platform: platform)
versions.find_by!(number: number, platform: platform, ruby_abi: ruby_abi)
end

def find_version_by_slug!(slug)
Expand Down
125 changes: 125 additions & 0 deletions test/functional/api/v1/deletions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,131 @@ class Api::V1::DeletionsControllerTest < ActionController::TestCase
end
end

context "for a gem with content-addressable versions across Ruby ABIs" do
setup do
@rubygem = create(:rubygem, name: "sandworm")
@abi32 = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: "~> 3.2.0", ruby_abi: "3.2", sha256: Digest::SHA2.base64digest("sandworm-1.0.0-3.2"))
@abi34 = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: "~> 3.4.0", ruby_abi: "3.4", sha256: Digest::SHA2.base64digest("sandworm-1.0.0-3.4"))
create(:ownership, user: @user, rubygem: @rubygem)
RubygemFs.instance.store("gems/#{@abi32.full_name}.gem", "")
RubygemFs.instance.store("gems/#{@abi34.full_name}.gem", "")
end

context "ON DELETE with a ruby_abi param" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", platform: "x86_64-linux-musl", ruby_abi: "3.2" }
end

should respond_with :success

should "yank only the targeted Ruby ABI variant" do
refute_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond with the deleted variant including its content address and Ruby ABI" do
assert_includes @response.body, "Successfully deleted gem: sandworm (1.0.0-724c5206, Platform: x86_64-linux-musl, Ruby ABI 3.2)"
end
end

context "ON DELETE without a ruby_abi param" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", platform: "x86_64-linux-musl" }
end

should respond_with :not_found

should "not yank either Ruby ABI variant" do
assert_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond that the version does not exist" do
assert_includes @response.body, "The version 1.0.0 (x86_64-linux-musl) does not exist."
end
end

context "ON DELETE with a ruby_abi param that does not match any variant" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", platform: "x86_64-linux-musl", ruby_abi: "3.3" }
end

should respond_with :not_found

should "not yank either Ruby ABI variant" do
assert_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond that the version does not exist" do
assert_includes @response.body, "The version 1.0.0 (x86_64-linux-musl) (Ruby ABI 3.3) does not exist."
end
end

context "ON DELETE with a ruby_abi param but no platform" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", ruby_abi: "3.2" }
end

should respond_with :bad_request

should "not yank either Ruby ABI variant" do
assert_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond that the platform param is required" do
assert_includes @response.body, "The platform param is required when ruby_abi is specified."
end
end

context "when a version supporting multiple Ruby ABIs coexists" do
setup do
@multi_abi = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: ">= 3.2")
RubygemFs.instance.store("gems/#{@multi_abi.full_name}.gem", "")
end

context "ON DELETE with only a platform param" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", platform: "x86_64-linux-musl" }
end

should respond_with :success

should "yank only the version supporting multiple Ruby ABIs and keep the single ABI variants" do
refute_predicate @multi_abi.reload, :indexed?
assert_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond with the deleted platform version" do
assert_includes @response.body, "Successfully deleted gem: sandworm (1.0.0-x86_64-linux-musl)"
end
end

context "ON DELETE with an empty ruby_abi param" do
setup do
delete :create, params: { gem_name: @rubygem.slug, version: "1.0.0", platform: "x86_64-linux-musl", ruby_abi: "" }
end

should respond_with :success

should "treat the empty ruby_abi as absent and yank the version supporting multiple Ruby ABIs" do
refute_predicate @multi_abi.reload, :indexed?
assert_predicate @abi32.reload, :indexed?
assert_predicate @abi34.reload, :indexed?
end

should "respond with the deleted platform version" do
assert_includes @response.body, "Successfully deleted gem: sandworm (1.0.0-x86_64-linux-musl)"
end
end
end
end

context "for a gem SomeGem with a version 0.1.0" do
setup do
@rubygem = create(:rubygem, name: "SomeGem")
Expand Down
13 changes: 13 additions & 0 deletions test/models/rubygem_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ class RubygemTest < ActiveSupport::TestCase
assert_equal version3_ruby, @rubygem.most_recent_version
end

should "find versions by number, platform and Ruby ABI" do
plain = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: ">= 3.2")
abi34 = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: "~> 3.4.0", ruby_abi: "3.4", sha256: Digest::SHA2.base64digest("abi34-1.0.0"))

assert_equal plain, @rubygem.find_version!(number: "1.0.0", platform: "x86_64-linux-musl")
assert_equal abi34, @rubygem.find_version!(number: "1.0.0", platform: "x86_64-linux-musl", ruby_abi: "3.4")
assert_raises(ActiveRecord::RecordNotFound) do
@rubygem.find_version!(number: "1.0.0", platform: "x86_64-linux-musl", ruby_abi: "3.2")
end
end

should "mark the latest version for each Ruby ABI per platform" do
abi32_old = create(:version, rubygem: @rubygem, number: "1.0.0", platform: "x86_64-linux-musl", gem_platform: "x86_64-linux-musl",
required_ruby_version: "~> 3.2.0", ruby_abi: "3.2", sha256: Digest::SHA2.base64digest("abi32-1.0.0"))
Expand Down
Loading