diff --git a/app/models/packages/conan/file_metadatum.rb b/app/models/packages/conan/file_metadatum.rb index c8de1df5effb242a19590576f20141ec0997bdf4..65ff3e0e156e04a3c886d6aeca742da52811ec42 100644 --- a/app/models/packages/conan/file_metadatum.rb +++ b/app/models/packages/conan/file_metadatum.rb @@ -23,6 +23,7 @@ class Packages::Conan::FileMetadatum < ApplicationRecord PACKAGE_FILES = ::Gitlab::Regex::Packages::CONAN_PACKAGE_FILES PACKAGE_BINARY = 'conan_package.tgz' CONAN_MANIFEST = 'conanmanifest.txt' + CONANINFO_TXT = 'conaninfo.txt' def recipe_revision_value recipe_revision&.revision || DEFAULT_REVISION diff --git a/app/services/packages/conan/create_package_file_service.rb b/app/services/packages/conan/create_package_file_service.rb index e1c35b4e156a609fcef26bb8028a2073511374f9..98bd50b1e391a1264e7127d5f9208dab2f055c41 100644 --- a/app/services/packages/conan/create_package_file_service.rb +++ b/app/services/packages/conan/create_package_file_service.rb @@ -34,6 +34,10 @@ def execute package_file.save! end + if package_file.file_name == ::Packages::Conan::FileMetadatum::CONANINFO_TXT && Feature.enabled?(:parse_conan_metadata_on_upload, Project.actor_from_id(package_file.project_id)) + ::Packages::Conan::ProcessPackageFileWorker.perform_async(package_file.id) + end + ServiceResponse.success(payload: { package_file: package_file }) rescue ActiveRecord::RecordInvalid => e ServiceResponse.error(message: e.message, reason: :invalid_package_file) diff --git a/app/services/packages/conan/metadata_extraction_service.rb b/app/services/packages/conan/metadata_extraction_service.rb new file mode 100644 index 0000000000000000000000000000000000000000..1d8a78cce537ee747b4c33006a4a51fef9cfaf63 --- /dev/null +++ b/app/services/packages/conan/metadata_extraction_service.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module Packages + module Conan + class MetadataExtractionService + include Gitlab::Utils::StrongMemoize + + ExtractionError = Class.new(StandardError) + + SECTION_HEADER_REGEX = /^\[(\w{1,50})\]$/ + RECIPE_HASH_SECTION = 'recipe_hash' + ALLOWED_SECTIONS = %w[requires recipe_hash options settings].freeze + + def initialize(package_file) + @package_file = package_file + end + + def execute + result = parse_conaninfo_content + + package_reference.update!(info: result) + ServiceResponse.success + rescue ActiveRecord::RecordInvalid => e + error_message = e.record.errors.full_messages.first + raise ExtractionError, "conaninfo.txt file too large" if error_message.include?("Info conaninfo is too large") + + raise ExtractionError, "conaninfo.txt metadata failedto be saved: #{error_message}" + end + + private + + attr_reader :package_file + + def parse_conaninfo_content + package_file.file.use_open_file(unlink_early: false) do |open_file| + parse_file_contents(File.open(open_file.file_path)) + end + rescue StandardError => e + raise ExtractionError, "Error while parsing conaninfo.txt: #{e.message}" + end + + def parse_file_contents(file) + result = {} + current_section = current_data = nil + + file.each do |line| + line = line.strip + next if line.empty? + + section_name = section_name_from(line) + if section_name + if current_section && current_data && allowed_section?(current_section) + result[current_section] = current_data + end + + current_section = section_name + current_data = nil + elsif current_section && allowed_section?(current_section) + validate_recipe_hash_section(current_section, current_data) + current_data = process_section_line(current_section, line, current_data) + end + end + + result[current_section] = current_data if current_section && current_data && allowed_section?(current_section) + result + end + + def validate_recipe_hash_section(section, data) + return unless section == RECIPE_HASH_SECTION && data + + raise ExtractionError, 'The recipe_hash section cannot have multiple lines' + end + + def process_section_line(section, line, data) + return line if section == RECIPE_HASH_SECTION + return process_key_value_line(line, data) if line.include?('=') + + process_array_line(line, data) + end + + def process_key_value_line(line, data) + data ||= {} + key, value = line.split('=', 2).map(&:strip) + raise ExtractionError, "Invalid key-value line: #{line}" if key.empty? || value.empty? + + data[key] = value + data + end + + def process_array_line(line, data) + data ||= [] + data << line + end + + def allowed_section?(section) + strong_memoize_with(:allowed_section, section) do + ALLOWED_SECTIONS.include?(section) + end + end + + def section_name_from(line) + section = line.match(SECTION_HEADER_REGEX)&.captures&.first + raise ExtractionError, "Invalid section header: #{line}" if section.nil? && line.start_with?('[') + + section + end + + def package_reference + package_file.conan_file_metadatum.package_reference + end + end + end +end diff --git a/app/validators/json_schemas/conan_package_info.json b/app/validators/json_schemas/conan_package_info.json index 3e2df2d8bc0fc12e6df93402550c3aaf9b07f9da..8f1607329280f87d844da7bd449bdd73f69c6afc 100644 --- a/app/validators/json_schemas/conan_package_info.json +++ b/app/validators/json_schemas/conan_package_info.json @@ -1,33 +1,28 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "description": "Conan Info Schema", - "oneOf": [ - { + "type": "object", + "properties": { + "recipe_hash": { + "type": "string" + }, + "settings": { "type": "object", - "additionalProperties": false + "additionalProperties": { + "type": "string" + } }, - { + "options": { "type": "object", - "properties": { - "settings": { - "type": "object" - }, - "requires": { - "type": "array", - "items": { - "type": "string" - } - }, - "options": { - "type": "object" - } - }, - "required": [ - "settings", - "requires", - "options" - ], - "additionalProperties": true + "additionalProperties": { + "type": "string" + } + }, + "requires": { + "type": "array", + "items": { + "type": "string" + } } - ] + } } diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 0140d207b7638b2cf9427023dca43d5892947719..4d3d0fa852dcd86f644d3b7db2567d1d7e2bb316 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -2134,6 +2134,16 @@ :tags: - :cronjob_child :queue_namespace: :package_cleanup +- :name: package_repositories:packages_conan_process_package_file + :worker_name: Packages::Conan::ProcessPackageFileWorker + :feature_category: :package_registry + :has_external_dependencies: false + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] + :queue_namespace: :package_repositories - :name: package_repositories:packages_debian_generate_distribution :worker_name: Packages::Debian::GenerateDistributionWorker :feature_category: :package_registry diff --git a/app/workers/concerns/packages/error_handling.rb b/app/workers/concerns/packages/error_handling.rb index a1b1b58aad52eb3094b4a4c0a8371517b85f212b..1ca125ee14fbe4ebd5fb77e70dfb6ec48da00961 100644 --- a/app/workers/concerns/packages/error_handling.rb +++ b/app/workers/concerns/packages/error_handling.rb @@ -17,7 +17,8 @@ module ErrorHandling ::Packages::Rubygems::ProcessGemService::ExtractionError, ::Packages::Rubygems::ProcessGemService::InvalidMetadataError, ::Packages::Npm::ProcessPackageFileService::ExtractionError, - ::Packages::Npm::CheckManifestCoherenceService::MismatchError + ::Packages::Npm::CheckManifestCoherenceService::MismatchError, + ::Packages::Conan::MetadataExtractionService::ExtractionError ].freeze def process_package_file_error(package_file:, exception:, extra_log_payload: {}) diff --git a/app/workers/packages/conan/process_package_file_worker.rb b/app/workers/packages/conan/process_package_file_worker.rb new file mode 100644 index 0000000000000000000000000000000000000000..0c451e2de91ceeab79c037949f698e9184697a78 --- /dev/null +++ b/app/workers/packages/conan/process_package_file_worker.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Packages + module Conan + class ProcessPackageFileWorker + include ApplicationWorker + + data_consistency :sticky + queue_namespace :package_repositories + feature_category :package_registry + deduplicate :until_executed + idempotent! + + def perform(package_file_id) + package_file = ::Packages::PackageFile.find_by_id(package_file_id) + return unless package_file + + ::Packages::Conan::MetadataExtractionService.new(package_file).execute + rescue StandardError => exception + logger.warn( + message: "Error processing conaninfo.txt file", + error: exception.message, + package_file: package_file.id, + project_id: package_file.project_id, + package_name: package_file.package.name, + package_version: package_file.package.version + ) + end + end + end +end diff --git a/config/feature_flags/gitlab_com_derisk/parse_conan_metadata_on_upload.yml b/config/feature_flags/gitlab_com_derisk/parse_conan_metadata_on_upload.yml new file mode 100644 index 0000000000000000000000000000000000000000..ab0715d3e404bba02f692ab954db674e13662df9 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/parse_conan_metadata_on_upload.yml @@ -0,0 +1,9 @@ +--- +name: parse_conan_metadata_on_upload +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/33892 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178728 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/523693 +milestone: '17.10' +group: group::package registry +type: gitlab_com_derisk +default_enabled: false diff --git a/spec/factories/packages/conan/package_references.rb b/spec/factories/packages/conan/package_references.rb index 792cd43b097db614c66252b26296c35214138186..dc9553b2b0445d3bf02c12898ddca5af00130015 100644 --- a/spec/factories/packages/conan/package_references.rb +++ b/spec/factories/packages/conan/package_references.rb @@ -9,7 +9,7 @@ { settings: { os: 'Linux', arch: 'x86_64' }, requires: ['libA/1.0@user/testing'], - options: { fPIC: true }, + options: { fPIC: 'True' }, otherProperties: 'some_value' } end diff --git a/spec/factories/packages/package_files.rb b/spec/factories/packages/package_files.rb index 7c178322681e67b7db15068210a4763159623ff3..efbc8afb55af7d22eb5091a4e03419abf14e67ce 100644 --- a/spec/factories/packages/package_files.rb +++ b/spec/factories/packages/package_files.rb @@ -84,11 +84,16 @@ end end - file_fixture { 'spec/fixtures/packages/conan/package_files/conaninfo.txt' } + transient do + conaninfo_fixture { 'conaninfo.txt' } + fixture_path { "spec/fixtures/packages/conan/package_files/#{conaninfo_fixture}" } + end + + file_fixture { fixture_path } file_name { 'conaninfo.txt' } file_sha1 { 'be93151dc23ac34a82752444556fe79b32c7a1ad' } file_md5 { '12345abcde' } - size { 400.kilobytes } + size { File.size(fixture_path) } end trait(:conan_package) do diff --git a/spec/fixtures/packages/conan/package_files/conaninfo.txt b/spec/fixtures/packages/conan/package_files/conaninfo.txt index 2a02515a19b36d9b3a29f610b67a683f585f5024..2da2c19cf9460b323cee00434a1eff3a5e12ef6f 100644 --- a/spec/fixtures/packages/conan/package_files/conaninfo.txt +++ b/spec/fixtures/packages/conan/package_files/conaninfo.txt @@ -7,7 +7,9 @@ os=Macos [requires] - + cli11/1.Y.Z + fmt/7.Y.Z + prometheus-cpp/1.Y.Z [options] shared=False @@ -21,7 +23,13 @@ os=Macos [full_requires] - + civetweb/1.15:77e8df9f2be98ef80d2a9f31ea49eb14597b20b0 + cli11/1.9.1:5ab84d6acfe1f23c4fae0ab88f26e3a396351ac9 + fmt/7.1.3:80138d4a58def120da0b8c9199f2b7a4e464a85b + libcurl/7.86.0:a097455223234e250d01a2687cf7c15446fbd5d5 + openssl/1.1.1s:6841fe8f0f22f6fa260da36a43a94ab525c7ed8d + prometheus-cpp/1.1.0:b39e1754fc610f750a6d595455854696692ec5bc + zlib/1.2.13:6841fe8f0f22f6fa260da36a43a94ab525c7ed8d [full_options] shared=False @@ -30,4 +38,5 @@ b4b91125b36b40a7076a98310588f820 [env] - + CC=clang-10 + CXX=clang++-10 diff --git a/spec/fixtures/packages/conan/package_files/conaninfo_invalid_line.txt b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_line.txt new file mode 100644 index 0000000000000000000000000000000000000000..156f3a591434d8eb029114698c921fdcc01e8a95 --- /dev/null +++ b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_line.txt @@ -0,0 +1,7 @@ +[settings] + arch=x86_64 + test= + another_test=123 + +[recipe_hash] + abc123def456 diff --git a/spec/fixtures/packages/conan/package_files/conaninfo_invalid_recipe_hash.txt b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_recipe_hash.txt new file mode 100644 index 0000000000000000000000000000000000000000..1d6b9d901bcb67324dc95a0e2e4428aea0694d52 --- /dev/null +++ b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_recipe_hash.txt @@ -0,0 +1,9 @@ +[settings] + arch=x86_64 + +[recipe_hash] + abc123def456 + another_hash_line_not_allowed + +[env] + CC=clang-10 diff --git a/spec/fixtures/packages/conan/package_files/conaninfo_invalid_section.txt b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_section.txt new file mode 100644 index 0000000000000000000000000000000000000000..9252abf88aab512f5950b85c83dc56b03f36d226 --- /dev/null +++ b/spec/fixtures/packages/conan/package_files/conaninfo_invalid_section.txt @@ -0,0 +1,24 @@ +[requires] +some_package/1.0.0 + +[options] +shared=False + +[full_settings] + arch=x86_64 + build_type=Release + +[full_requires] +some_package/1.0.0 + +[full_options] + shared=False + +[recipe_hash] + a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 + +[missing_bracket + this_section_is_not_properly_closed + +[env] + SOME_ENV_VAR=value diff --git a/spec/fixtures/packages/conan/package_files/conaninfo_minimal.txt b/spec/fixtures/packages/conan/package_files/conaninfo_minimal.txt new file mode 100644 index 0000000000000000000000000000000000000000..a034f9243fea30bcadf0b5694e4e69714601a0fa --- /dev/null +++ b/spec/fixtures/packages/conan/package_files/conaninfo_minimal.txt @@ -0,0 +1,21 @@ +[settings] + arch=x86_64 + build_type=Release + compiler=apple-clang + compiler.libcxx=libc++ + compiler.version=10.0 + os=Macos + +[requires] + +[options] + +[full_settings] + +[full_requires] + +[full_options] + +[recipe_hash] + +[env] diff --git a/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo.json b/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo.json new file mode 100644 index 0000000000000000000000000000000000000000..e090f09a98a070b4277a620ac183bf4a5a01337c --- /dev/null +++ b/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo.json @@ -0,0 +1,19 @@ +{ + "settings": { + "arch": "x86_64", + "build_type": "Release", + "compiler": "apple-clang", + "compiler.libcxx": "libc++", + "compiler.version": "10.0", + "os": "Macos" + }, + "requires": [ + "cli11/1.Y.Z", + "fmt/7.Y.Z", + "prometheus-cpp/1.Y.Z" + ], + "options": { + "shared": "False" + }, + "recipe_hash": "b4b91125b36b40a7076a98310588f820" +} diff --git a/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo_minimal.json b/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo_minimal.json new file mode 100644 index 0000000000000000000000000000000000000000..f13ad503c28b779d0c183ba2d114c15aca2deef4 --- /dev/null +++ b/spec/fixtures/packages/conan/parsed_conaninfo/conaninfo_minimal.json @@ -0,0 +1,10 @@ +{ + "settings": { + "arch": "x86_64", + "build_type": "Release", + "compiler": "apple-clang", + "compiler.libcxx": "libc++", + "compiler.version": "10.0", + "os": "Macos" + } +} diff --git a/spec/models/packages/conan/package_reference_spec.rb b/spec/models/packages/conan/package_reference_spec.rb index 6ad31a00f495f14b5e66a930ee6a1bd1282b8cec..a116879327c955acb25bbbd880b9f7499fe57425 100644 --- a/spec/models/packages/conan/package_reference_spec.rb +++ b/spec/models/packages/conan/package_reference_spec.rb @@ -118,12 +118,12 @@ end context 'with invalid conan info' do - let(:info) { { invalid_field: 'some_value' } } + let(:info) { { settings: 'incorrect_value' } } it 'is invalid', :aggregate_failures do expect(package_reference).not_to be_valid expect(package_reference.errors[:info]).to include( - 'object at root is missing required properties: settings, requires, options') + 'value at `/settings` is not an object') end end diff --git a/spec/services/packages/conan/create_package_file_service_spec.rb b/spec/services/packages/conan/create_package_file_service_spec.rb index fc19aaabe8aadd37a2832da6a7e39dea9b9e53b9..063ed6be21121ff50b68eb53733644a6cb0e9010 100644 --- a/spec/services/packages/conan/create_package_file_service_spec.rb +++ b/spec/services/packages/conan/create_package_file_service_spec.rb @@ -213,5 +213,68 @@ expect { response }.not_to change { Packages::Conan::PackageReference.count } end end + + context 'queueing the conan package file processing worker' do + before do + allow(::Packages::Conan::ProcessPackageFileWorker).to receive(:perform_async) + end + + let(:params) do + { + file_name: file_name, + 'file.md5': '12345', + 'file.sha1': '54321', + 'file.size': '128', + 'file.type': 'txt', + recipe_revision: '0', + package_revision: '0', + conan_package_reference: '123456789', + conan_file_type: :package_file + }.with_indifferent_access + end + + context 'when the filename is conaninfo.txt' do + let(:file_name) { 'conaninfo.txt' } + + let(:file) do + fixture_file_upload('spec/fixtures/packages/conan/package_files/conaninfo.txt', 'text/plain') + end + + context 'when the feature flag is enabled' do + before do + stub_feature_flags(parse_conan_metadata_on_upload: true) + end + + it 'queues the Conan package file processing worker' do + expect(response).to be_success + expect(::Packages::Conan::ProcessPackageFileWorker).to have_received(:perform_async) + .with(response[:package_file].id) + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(parse_conan_metadata_on_upload: false) + end + + it 'does not queue the Conan package file processing worker' do + expect(response).to be_success + expect(::Packages::Conan::ProcessPackageFileWorker).not_to have_received(:perform_async) + end + end + end + + context 'when the filename is not conaninfo.txt' do + include_context 'with temp file setup' + + let(:file_name) { 'not_conaninfo.txt' } + + it 'does not queue the Conan package file processing worker' do + expect(::Packages::Conan::ProcessPackageFileWorker).not_to receive(:perform_async) + + response + end + end + end end end diff --git a/spec/services/packages/conan/metadata_extraction_service_spec.rb b/spec/services/packages/conan/metadata_extraction_service_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d85785af2d72442c3104073b3011b1d6c6af2ee1 --- /dev/null +++ b/spec/services/packages/conan/metadata_extraction_service_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Conan::MetadataExtractionService, feature_category: :package_registry do + using RSpec::Parameterized::TableSyntax + + let_it_be(:package_reference) { create(:conan_package_reference, info: {}) } + let_it_be(:package_file) do + create(:conan_package_file, :conan_package_info, conan_package_reference: package_reference) + end + + describe '#execute' do + subject(:service) { described_class.new(package_file).execute } + + describe 'parsing conaninfo files' do + let(:expected_metadata) do + Gitlab::Json.parse(fixture_file_upload("spec/fixtures/packages/conan/parsed_conaninfo/#{expected_json}").read) + end + + where(:conaninfo_fixture, :expected_json) do + 'conaninfo.txt' | 'conaninfo.json' + 'conaninfo_minimal.txt' | 'conaninfo_minimal.json' + end + + with_them do + before do + package_file.file = fixture_file_upload("spec/fixtures/packages/conan/package_files/#{conaninfo_fixture}") + end + + it 'updates the package reference info', :aggregate_failures do + expect { service } + .to change { package_file.conan_file_metadatum.package_reference.reload.info } + .from({}) + .to(expected_metadata) + end + end + end + + context 'with database error' do + # rubocop:disable Layout/LineLength -- Required for formatting of table + where(:database_error_message, :expected_error_message) do + 'Info conaninfo is too large. Maximum size is 20000 characters' | 'conaninfo.txt file too large' + 'Test error' | 'conaninfo.txt metadata failedto be saved: Test error' + end + # rubocop:enable Layout/LineLength + + with_them do + before do + allow(package_reference).to receive(:update!).and_raise(ActiveRecord::RecordInvalid.new(package_reference)) + allow(package_reference).to receive_message_chain(:errors, + :full_messages).and_return([database_error_message]) + end + + it 'raises ExtractionError and does not update package reference info' do + expect { service } + .to raise_error(described_class::ExtractionError, expected_error_message) + .and not_change { package_reference.reload.info } + end + end + end + + context 'with invalid conaninfo.txt' do + # rubocop:disable Layout/LineLength -- Required for formatting of table + where(:conaninfo_fixture, :expected_error) do + 'conaninfo_invalid_line.txt' | 'Error while parsing conaninfo.txt: Invalid key-value line: test=' + 'conaninfo_invalid_recipe_hash.txt' | 'Error while parsing conaninfo.txt: The recipe_hash section cannot have multiple lines' + 'conaninfo_invalid_section.txt' | 'Error while parsing conaninfo.txt: Invalid section header: [missing_bracket' + end + # rubocop:enable Layout/LineLength + + with_them do + before do + package_file.file = fixture_file_upload("spec/fixtures/packages/conan/package_files/#{conaninfo_fixture}") + end + + it 'raises ExtractionError and does not update package reference info' do + expect { service } + .to raise_error(described_class::ExtractionError, expected_error) + .and not_change { package_file.conan_file_metadatum.package_reference.reload.info } + end + end + end + end +end diff --git a/spec/workers/packages/conan/process_package_file_worker_spec.rb b/spec/workers/packages/conan/process_package_file_worker_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..95b248bee9f088489721dca452421b15ac791d52 --- /dev/null +++ b/spec/workers/packages/conan/process_package_file_worker_spec.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Packages::Conan::ProcessPackageFileWorker, type: :worker, feature_category: :package_registry do + let_it_be(:package_file) { create(:conan_package_file, :conan_package_info) } + + let(:worker) { described_class.new } + let(:package_file_id) { package_file.id } + + describe "#perform" do + subject(:perform) { worker.perform(package_file_id) } + + it_behaves_like 'an idempotent worker' do + let(:job_args) { package_file_id } + end + + it_behaves_like 'worker with data consistency', described_class, data_consistency: :sticky + + it 'has :until_executed deduplicate strategy' do + expect(described_class.get_deduplicate_strategy).to eq(:until_executed) + end + + context 'with existing package file' do + it 'calls the MetadataExtractionService' do + expect_next_instance_of(::Packages::Conan::MetadataExtractionService, package_file) do |service| + expect(service).to receive(:execute) + end + + perform + end + + context 'when service raises an error' do + let(:exception) { ::Packages::Conan::MetadataExtractionService::ExtractionError.new('test error') } + let(:logger) { instance_double(::Logger) } + + before do + allow_next_instance_of(::Packages::Conan::MetadataExtractionService) do |service| + allow(service).to receive(:execute).and_raise(exception) + end + allow(logger).to receive(:warn) + allow(worker).to receive(:logger).and_return(logger) + end + + it 'processes the error through error handling concern' do + expect(logger).to receive(:warn).with( + message: "Error processing conaninfo.txt file", + error: exception.message, + package_file: package_file.id, + project_id: package_file.project_id, + package_name: package_file.package.name, + package_version: package_file.package.version + ) + + perform + end + end + end + + context 'with a non-existing package file' do + let(:package_file_id) { non_existing_record_id } + + it 'does not call the service' do + expect(::Packages::Conan::MetadataExtractionService).not_to receive(:new) + + perform + end + end + end +end