diff --git a/ee/app/models/package_metadata/affected_package.rb b/ee/app/models/package_metadata/affected_package.rb
index 23e3ba0aed74b054a50337ab3be779252ed90b20..5cd7dd51c66d4d1dc60fb1f408c69cb06a3dd715 100644
--- a/ee/app/models/package_metadata/affected_package.rb
+++ b/ee/app/models/package_metadata/affected_package.rb
@@ -40,5 +40,15 @@ def self.occurrence_cte_join
       'INNER JOIN occurrences_cte ON occurrences_cte.purl_type = pm_affected_packages.purl_type ' \
         'AND occurrences_cte.name = pm_affected_packages.package_name'
     end
+
+    def solution_text
+      return solution if solution.present?
+
+      # This is a Container Scanning affected package, check for presence of fixed_versions.
+      explicit_fixed_version = fixed_versions.delete_if { |v| v == '*' }
+      return 'Unfortunately, there is no solution available yet.' if explicit_fixed_version.empty?
+
+      "Upgrade to version #{explicit_fixed_version.join(', ')} or above"
+    end
   end
 end
diff --git a/ee/lib/gitlab/vulnerability_scanning/advisory.rb b/ee/lib/gitlab/vulnerability_scanning/advisory.rb
index f7f493a3e3950cc3c51c33fe6b5a3f51242098fe..7a60217817425cccb73c3b20bf40811c42a30f2e 100644
--- a/ee/lib/gitlab/vulnerability_scanning/advisory.rb
+++ b/ee/lib/gitlab/vulnerability_scanning/advisory.rb
@@ -5,6 +5,20 @@ module VulnerabilityScanning
     class Advisory
       attr_reader :xid, :title, :description, :solution, :identifiers, :urls, :cvss_v2, :cvss_v3, :source_xid
 
+      def self.from_affected_package(affected_package:, advisory:)
+        new(
+          xid: advisory.advisory_xid,
+          title: advisory.title,
+          description: advisory.description,
+          identifiers: advisory.identifiers,
+          urls: advisory.urls,
+          cvss_v2: advisory.cvss_v2,
+          cvss_v3: advisory.cvss_v3,
+          solution: affected_package.solution_text,
+          source_xid: advisory.source_xid
+        )
+      end
+
       # rubocop:disable Metrics/ParameterLists
       # Creates a new advisory object that can be used to create findings
       # via the Gitlab::VulnerabilityScanning::FindingBuilder classes.
diff --git a/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb b/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb
index bd4be948a8de5ae107c6025d4ed75e0c6d7ee469..f44ca7fdb5c50ca3dd69e057b8baa2dc90054663 100644
--- a/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb
+++ b/ee/lib/gitlab/vulnerability_scanning/advisory_scanner.rb
@@ -4,6 +4,7 @@ module Gitlab
   module VulnerabilityScanning
     class AdvisoryScanner
       include Gitlab::Utils::StrongMemoize
+      include Gitlab::VulnerabilityScanning::AdvisoryUtils
 
       # Scans eligible projects that contain software components affected
       # by an advisory. If affected, it creates new vulnerabilities in the
@@ -31,7 +32,8 @@ def execute
         start_time = Time.current.iso8601
 
         affected_packages.each do |affected_package|
-          advisory_data_object = vulnerability_scanning_advisory(solution: solution(affected_package))
+          advisory_data_object = Gitlab::VulnerabilityScanning::Advisory.from_affected_package(
+            affected_package: affected_package, advisory: advisory)
           purl_type = affected_package.purl_type
           package_name = affected_package.package_name
           ::Sbom::PossiblyAffectedOccurrencesFinder.new(
@@ -72,66 +74,6 @@ def affected_packages
       end
       strong_memoize_attr :affected_packages
 
-      def occurrence_is_affected?(purl_type:, range:, version:, distro:, source:, project_id:)
-        matcher = build_matcher(purl_type: purl_type, range: range)
-        if Enums::Sbom.container_scanning_purl_type?(purl_type)
-          matcher.affected?(distro, source, version)
-        else
-          matcher.affected?(version)
-        end
-      rescue SemverDialects::InvalidVersionError, SemverDialects::UnsupportedVersionError => error
-        log_cannot_determine_if_occurence_is_affected(error: error, purl_type: purl_type, version: version,
-          project_id: project_id)
-        false
-      end
-
-      def build_matcher(purl_type:, range:)
-        strong_memoize_with(:build_matcher, purl_type, range) do
-          if Enums::Sbom.container_scanning_purl_type?(purl_type)
-            Gitlab::VulnerabilityScanning::ContainerScanning::AffectedVersionRangeMatcher.new(
-              purl_type: purl_type, range: range)
-          else
-            Gitlab::VulnerabilityScanning::DependencyScanning::AffectedVersionRangeMatcher.new(
-              purl_type: purl_type, range: range)
-          end
-        end
-      end
-
-      def log_cannot_determine_if_occurence_is_affected(error:, purl_type:, version:, project_id:)
-        ::Gitlab::ErrorTracking.track_exception(
-          error,
-          message: 'Cannot determine if component is affected',
-          purl_type: purl_type,
-          version: version,
-          project_id: project_id,
-          advisory_xid: advisory.advisory_xid,
-          source_xid: advisory.source_xid)
-      end
-
-      def vulnerability_scanning_advisory(solution:)
-        Gitlab::VulnerabilityScanning::Advisory.new(
-          xid: advisory.advisory_xid,
-          title: advisory.title,
-          description: advisory.description,
-          identifiers: advisory.identifiers,
-          urls: advisory.urls,
-          cvss_v2: advisory.cvss_v2,
-          cvss_v3: advisory.cvss_v3,
-          solution: solution,
-          source_xid: advisory.source_xid
-        )
-      end
-
-      def solution(affected_package)
-        return affected_package.solution if affected_package.solution.present?
-
-        # This is a Container Scanning affected package, check for presence of fixed_versions.
-        fixed_versions = affected_package.fixed_versions.delete_if { |v| v == '*' }
-        return 'Unfortunately, there is no solution available yet.' if fixed_versions.empty?
-
-        "Upgrade to version #{fixed_versions.join(', ')} or above"
-      end
-
       def bulk_vulnerability_ingestion(affected_package, advisory_data_object, occurrences_batch)
         affected_components = occurrences_batch.filter_map do |occurrence|
           count_possibly_affected_sbom_occurrence(occurrence)
@@ -153,21 +95,7 @@ def bulk_vulnerability_ingestion(affected_package, advisory_data_object, occurre
 
         return if affected_components.empty?
 
-        create_vulnerabilities(advisory_data_object, affected_components)
-      end
-
-      def create_vulnerabilities(advisory, affected_components)
-        response = ::Security::VulnerabilityScanning::CreateVulnerabilityService.execute(
-          advisory: advisory, affected_components: affected_components)
-
-        project_ids_with_upsert = response.payload[:project_ids_with_upsert]
-        project_ids_with_error = response.payload[:project_ids_with_error]
-        if response.success?
-          log_success(project_ids_with_upsert: project_ids_with_upsert, project_ids_with_error: project_ids_with_error)
-        else
-          log_error(response.payload[:error], project_ids_with_upsert: project_ids_with_upsert,
-            project_ids_with_error: project_ids_with_error)
-        end
+        create_vulnerabilities(advisory: advisory_data_object, affected_components: affected_components)
       end
 
       def count_possibly_affected_sbom_occurrence(occurrence)
@@ -187,18 +115,6 @@ def possibly_affected_projects_count
       def known_affected_projects_count
         @known_affected_projects.keys.size
       end
-
-      def log_success(project_ids_with_upsert:, project_ids_with_error:)
-        Gitlab::AppJsonLogger.debug(message: "Successfully created vulnerabilities on advisory ingestion",
-          project_ids_with_upsert: project_ids_with_upsert, project_ids_with_error: project_ids_with_error,
-          source_xid: advisory.source_xid, advisory_xid: advisory.advisory_xid)
-      end
-
-      def log_error(error, project_ids_with_upsert:, project_ids_with_error:)
-        Gitlab::AppJsonLogger.error(message: "Failed to create vulnerabilities on advisory ingestion", error: error,
-          project_ids_with_upsert: project_ids_with_upsert, project_ids_with_error: project_ids_with_error,
-          source_xid: advisory.source_xid, advisory_xid: advisory.advisory_xid)
-      end
     end
   end
 end
diff --git a/ee/lib/gitlab/vulnerability_scanning/advisory_utils.rb b/ee/lib/gitlab/vulnerability_scanning/advisory_utils.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b7274dfae0bef939db7bea51fde4125e85fd30c
--- /dev/null
+++ b/ee/lib/gitlab/vulnerability_scanning/advisory_utils.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module VulnerabilityScanning
+    module AdvisoryUtils
+      include Gitlab::Utils::StrongMemoize
+
+      def occurrence_is_affected?(purl_type:, range:, version:, distro:, source:, project_id:)
+        matcher = build_matcher(purl_type: purl_type, range: range)
+        if Enums::Sbom.container_scanning_purl_type?(purl_type)
+          matcher.affected?(distro, source, version)
+        else
+          matcher.affected?(version)
+        end
+      rescue SemverDialects::InvalidVersionError, SemverDialects::UnsupportedVersionError => error
+        track_indetermined_if_occurence_is_affected(error: error, purl_type: purl_type, version: version,
+          project_id: project_id)
+        false
+      end
+
+      def build_matcher(purl_type:, range:)
+        strong_memoize_with(:build_matcher, purl_type, range) do
+          if Enums::Sbom.container_scanning_purl_type?(purl_type)
+            Gitlab::VulnerabilityScanning::ContainerScanning::AffectedVersionRangeMatcher.new(
+              purl_type: purl_type, range: range)
+          else
+            Gitlab::VulnerabilityScanning::DependencyScanning::AffectedVersionRangeMatcher.new(
+              purl_type: purl_type, range: range)
+          end
+        end
+      end
+
+      def track_indetermined_if_occurence_is_affected(error:, purl_type:, version:, project_id:)
+        ::Gitlab::ErrorTracking.track_exception(
+          error,
+          message: 'Cannot determine if component is affected',
+          purl_type: purl_type,
+          version: version,
+          project_id: project_id)
+      end
+
+      def create_vulnerabilities(advisory:, affected_components:)
+        response = ::Security::VulnerabilityScanning::CreateVulnerabilityService.execute(
+          advisory: advisory, affected_components: affected_components)
+
+        project_ids_with_upsert = response.payload[:project_ids_with_upsert]
+        project_ids_with_error = response.payload[:project_ids_with_error]
+        if response.success?
+          log_success(advisory: advisory, project_ids_with_upsert: project_ids_with_upsert,
+            project_ids_with_error: project_ids_with_error)
+        else
+          log_error(response.payload[:error], advisory: advisory, project_ids_with_upsert: project_ids_with_upsert,
+            project_ids_with_error: project_ids_with_error)
+        end
+      end
+
+      def log_success(advisory:, project_ids_with_upsert:, project_ids_with_error:)
+        Gitlab::AppJsonLogger.debug(message: "Successfully created vulnerabilities on advisory ingestion",
+          project_ids_with_upsert: project_ids_with_upsert, project_ids_with_error: project_ids_with_error,
+          source_xid: advisory.source_xid, advisory_xid: advisory.xid)
+      end
+
+      def log_error(error, advisory:, project_ids_with_upsert:, project_ids_with_error:)
+        Gitlab::AppJsonLogger.error(message: "Failed to create vulnerabilities on advisory ingestion", error: error,
+          project_ids_with_upsert: project_ids_with_upsert, project_ids_with_error: project_ids_with_error,
+          source_xid: advisory.source_xid, advisory_xid: advisory.xid)
+      end
+    end
+  end
+end
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/advisory_scanner_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_scanner_spec.rb
index 116731debd92381e191d1e9fdf417ebc63581de2..f1fbf3670934b1fed22f39295f7a69f22e8eb56d 100644
--- a/ee/spec/lib/gitlab/vulnerability_scanning/advisory_scanner_spec.rb
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_scanner_spec.rb
@@ -120,16 +120,13 @@
         end
 
         it "captures and tracks the invalid version error" do
-          advisory = affected_package.advisory
           pipeline = affected_pipeline
           expect(Gitlab::ErrorTracking).to have_received(:track_exception)
                   .with(a_kind_of(::SemverDialects::InvalidVersionError),
                     message: 'Cannot determine if component is affected',
                     purl_type: 'npm',
                     version: 'invalid-version',
-                    project_id: pipeline.project.id,
-                    advisory_xid: advisory.advisory_xid,
-                    source_xid: advisory.source_xid)
+                    project_id: pipeline.project.id)
         end
 
         it "tracks an event for the scan" do
@@ -410,16 +407,13 @@
 
         it "captures and tracks the unsupported version error" do
           # APK package versions containing leading zeros eg 1.2.03 are currently unsupported. https://gitlab.com/gitlab-org/gitlab/-/issues/471509
-          advisory = affected_package.advisory
           pipeline = affected_pipeline
           expect(Gitlab::ErrorTracking).to have_received(:track_exception)
                   .with(a_kind_of(::SemverDialects::UnsupportedVersionError),
                     message: 'Cannot determine if component is affected',
                     purl_type: 'apk',
                     version: '1.2.03',
-                    project_id: pipeline.project.id,
-                    advisory_xid: advisory.advisory_xid,
-                    source_xid: advisory.source_xid)
+                    project_id: pipeline.project.id)
         end
       end
     end
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/advisory_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_spec.rb
index c7ef74b127caf7564f5b21728e754670d2384d20..654e3f2013bf2279e609c7800a41657b50981221 100644
--- a/ee/spec/lib/gitlab/vulnerability_scanning/advisory_spec.rb
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_spec.rb
@@ -64,4 +64,22 @@
       it { expect(advisory.cvss_v2).to be(cvss_v2) }
     end
   end
+
+  describe '.from_affected_package' do
+    let(:advisory) { affected_package.advisory }
+    let(:affected_package) { build(:pm_affected_package) }
+
+    subject do
+      described_class.from_affected_package(affected_package: affected_package,
+        advisory: advisory)
+    end
+
+    it 'returns data with attributes related to advisory and affected_package' do
+      is_expected.to have_attributes(
+        xid: advisory.advisory_xid, title: advisory.title,
+        description: advisory.description, identifiers: advisory.identifiers,
+        urls: advisory.urls, cvss_v2: advisory.cvss_v2, cvss_v3: advisory.cvss_v3,
+        source_xid: advisory.source_xid, solution: affected_package.solution)
+    end
+  end
 end
diff --git a/ee/spec/lib/gitlab/vulnerability_scanning/advisory_utils_spec.rb b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_utils_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5fc182bb44c861bf55ed9640335762fa72e58575
--- /dev/null
+++ b/ee/spec/lib/gitlab/vulnerability_scanning/advisory_utils_spec.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe Gitlab::VulnerabilityScanning::AdvisoryUtils, feature_category: :software_composition_analysis do
+  let(:advisory_utils_test_class) do
+    Class.new do
+      include Gitlab::VulnerabilityScanning::AdvisoryUtils
+    end
+  end
+
+  let(:affected_package) do
+    build(:pm_affected_package)
+  end
+
+  let(:advisory) { affected_package.advisory }
+  let_it_be(:user) { create(:user) }
+  let_it_be(:pipeline) { create(:ci_pipeline, user: user) }
+  let_it_be(:occurrence) { create(:sbom_occurrence, pipeline: pipeline) }
+
+  describe '.occurrence_is_affected?' do
+    let(:source) { occurrence.source }
+    let(:version) { "5.2" }
+
+    subject(:occurrence_is_affected) do
+      advisory_utils_test_class.new.occurrence_is_affected?(purl_type: affected_package.purl_type,
+        range: affected_package.affected_range, version: version, distro: affected_package.distro_version,
+        source: source, project_id: occurrence.project_id)
+    end
+
+    context 'when the occurrence is not affected' do
+      let(:version) { "1.0" }
+
+      it { is_expected.to be false }
+    end
+
+    context 'with container scanning' do
+      let(:affected_package) { build(:pm_affected_package, :os_advisory, affected_range: "<5:5.2") }
+      let(:source) do
+        create(:sbom_source, source_type: :container_scanning, packager_name: 'apk',
+          source: {
+            'category' => 'development',
+            'image' => { 'name' => 'image-1', 'tag' => 'v1' },
+            'operating_system' => { 'name' => 'debian', 'version' => '9' }
+          })
+      end
+
+      it { is_expected.to be true }
+
+      context 'when affected package version is not supported' do
+        # APK package versions containing leading zeros eg 1.2.03 are currently unsupported. https://gitlab.com/gitlab-org/gitlab/-/issues/471509
+        let(:affected_package) { build(:pm_affected_package, purl_type: "apk", distro_version: 'alpine 3.14') }
+        let(:version) { "5.03" }
+
+        let(:source) do
+          create(:sbom_source, source_type: :container_scanning, packager_name: 'apk',
+            source: {
+              'category' => 'development',
+              'image' => { 'name' => 'image-1', 'tag' => 'v1' },
+              'operating_system' => { 'name' => 'alpine', 'version' => '3.14' }
+            })
+        end
+
+        it "captures and tracks the unsupported version error" do
+          expect(Gitlab::ErrorTracking).to receive(:track_exception)
+                  .with(an_instance_of(SemverDialects::UnsupportedVersionError),
+                    message: 'Cannot determine if component is affected',
+                    purl_type: 'apk',
+                    version: version,
+                    project_id: occurrence.project_id).once
+
+          occurrence_is_affected
+        end
+      end
+    end
+
+    context 'with dependency scanning' do
+      it { is_expected.to be true }
+    end
+
+    context 'when version is invalid' do
+      let(:affected_package) { build(:pm_affected_package, purl_type: "pypi") }
+      let(:version) { " 5.0" }
+
+      it 'tracks an exception' do
+        expect(Gitlab::ErrorTracking).to receive(:track_exception)
+          .with(
+            an_instance_of(SemverDialects::InvalidVersionError),
+            message: 'Cannot determine if component is affected',
+            purl_type: affected_package.purl_type,
+            version: version,
+            project_id: occurrence.project_id
+          )
+          .once
+
+        occurrence_is_affected
+      end
+    end
+  end
+
+  describe '.create_vulnerabilities' do
+    subject(:create_vulnerabilities) do
+      advisory_utils_test_class.new.create_vulnerabilities(advisory: advisory_data, affected_components: [occurrence])
+    end
+
+    let(:advisory_data) do
+      Gitlab::VulnerabilityScanning::Advisory.from_affected_package(affected_package: affected_package,
+        advisory: advisory)
+    end
+
+    it 'creates new vulnerabilities' do
+      expect(Gitlab::AppJsonLogger).to receive(:debug)
+        .with(
+          message: "Successfully created vulnerabilities on advisory ingestion",
+          project_ids_with_upsert: [pipeline.project.id], project_ids_with_error: [],
+          source_xid: advisory_data.source_xid, advisory_xid: advisory_data.xid)
+        .once
+
+      expect { create_vulnerabilities }.to change { Vulnerability.count }.by(1)
+    end
+
+    context 'when exception is raised' do
+      before do
+        allow(::Security::Ingestion::IngestCvsSliceService).to receive(:execute).and_raise(StandardError)
+      end
+
+      it 'does not create vulnerabilities' do
+        expect(Gitlab::AppJsonLogger).to receive(:error)
+         .with(
+           message: "Failed to create vulnerabilities on advisory ingestion",
+           error: an_instance_of(StandardError),
+           project_ids_with_upsert: [pipeline.project.id], project_ids_with_error: [],
+           source_xid: advisory_data.source_xid, advisory_xid: advisory_data.xid)
+         .once
+
+        expect { create_vulnerabilities }.not_to change { Vulnerability.count }
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/package_metadata/affected_package_spec.rb b/ee/spec/models/package_metadata/affected_package_spec.rb
index 753b6ba6c100fc31d4db0dc800f9066a8040be2d..4507d678ba7a9ecdf201ca3d651077abd7f5e3ef 100644
--- a/ee/spec/models/package_metadata/affected_package_spec.rb
+++ b/ee/spec/models/package_metadata/affected_package_spec.rb
@@ -138,4 +138,33 @@
       expect(described_class.for_occurrences(occurrences)).not_to include(unrelated_affected_package)
     end
   end
+
+  describe '#solution_text' do
+    subject { affected_package.solution_text }
+
+    let(:affected_package) do
+      build(:pm_affected_package, solution: 'Update version')
+    end
+
+    context 'with solution present' do
+      it { is_expected.to eq('Update version') }
+    end
+
+    context 'without fixed versions' do
+      let(:affected_package) do
+        build(:pm_affected_package, solution: nil, fixed_versions: [])
+      end
+
+      it { is_expected.to eq('Unfortunately, there is no solution available yet.') }
+    end
+
+    context 'with fixed versions' do
+      let(:fixed_version) { '1.2.3' }
+      let(:affected_package) do
+        build(:pm_affected_package, solution: nil, fixed_versions: [fixed_version])
+      end
+
+      it { is_expected.to match(/Upgrade to version #{fixed_version} or above/) }
+    end
+  end
 end