diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index d02f3731cc267a78584f64f3b638a27b4072a361..74cbdb29abdddc700b1af232589c59ec9f13d7c4 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -4,7 +4,7 @@
#
# Contains common functionality shared between Issues and MergeRequests
#
-# Used by Issue, MergeRequest
+# Used by Issue, MergeRequest, Epic
#
module Issuable
extend ActiveSupport::Concern
@@ -26,6 +26,11 @@ module Issuable
include IssuableStates
include ClosedAtFilterable
+ TITLE_LENGTH_MAX = 255
+ TITLE_HTML_LENGTH_MAX = 800
+ DESCRIPTION_LENGTH_MAX = 16000
+ DESCRIPTION_HTML_LENGTH_MAX = 48000
+
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance.
@@ -72,10 +77,15 @@ def award_emojis_loaded?
prefix: true
validates :author, presence: true
- validates :title, presence: true, length: { maximum: 255 }
- validates :description, length: { maximum: Gitlab::Database::MAX_TEXT_SIZE_LIMIT }, allow_blank: true
+ validates :title, presence: true, length: { maximum: TITLE_LENGTH_MAX }
+ # we validate the description against DESCRIPTION_LENGTH_MAX only for Issuables being created
+ # to avoid breaking the existing Issuables which may have their descriptions longer
+ validates :description, length: { maximum: DESCRIPTION_LENGTH_MAX }, allow_blank: true, on: :create
+ validate :description_max_length_for_new_records_is_valid, on: :update
validate :milestone_is_valid
+ before_validation :truncate_description_on_import!
+
scope :authored, ->(user) { where(author_id: user) }
scope :recent, -> { reorder(id: :desc) }
scope :of_projects, ->(ids) { where(project_id: ids) }
@@ -138,6 +148,16 @@ def has_multiple_assignees?
def milestone_is_valid
errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available?
end
+
+ def description_max_length_for_new_records_is_valid
+ if new_record? && description.length > Issuable::DESCRIPTION_LENGTH_MAX
+ errors.add(:description, :too_long, count: Issuable::DESCRIPTION_LENGTH_MAX)
+ end
+ end
+
+ def truncate_description_on_import!
+ self.description = description&.slice(0, Issuable::DESCRIPTION_LENGTH_MAX) if importing?
+ end
end
class_methods do
diff --git a/db/migrate/20190929180751_create_vulnerabilities.rb b/db/migrate/20190929180751_create_vulnerabilities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..aea018c597928b7233c3b0b66e824f3801aaa378
--- /dev/null
+++ b/db/migrate/20190929180751_create_vulnerabilities.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateVulnerabilities < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :vulnerabilities do |t|
+ t.bigint "milestone_id"
+ t.bigint "epic_id"
+ t.bigint "project_id", null: false
+ t.bigint "author_id", null: false
+ t.bigint "updated_by_id"
+ t.bigint "last_edited_by_id"
+ t.bigint "start_date_sourcing_milestone_id"
+ t.bigint "due_date_sourcing_milestone_id"
+ t.bigint "closed_by_id"
+ t.datetime_with_timezone "last_edited_at"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.datetime_with_timezone "closed_at"
+ t.date "start_date"
+ t.date "due_date"
+ t.integer "state", limit: 2, default: 1, null: false # initially: open, closed
+ t.integer "severity", limit: 2, null: false # auto-calculated as highest-severity finding, but overrideable
+ t.integer "confidence", limit: 2, null: false # auto-calculated as lowest-confidence finding, but overrideable
+ t.boolean "severity_overridden", default: false
+ t.boolean "confidence_overridden", default: false
+ t.string "title", limit: 255, null: false
+ t.text "title_html", null: false
+ t.text "description"
+ t.text "description_html"
+ end
+ end
+end
diff --git a/db/migrate/20190929180813_add_reference_from_vulnerability_occurrences_to_occurrences.rb b/db/migrate/20190929180813_add_reference_from_vulnerability_occurrences_to_occurrences.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb8e8fb73d127cd0b23655af71e2bc2dfe6aa789
--- /dev/null
+++ b/db/migrate/20190929180813_add_reference_from_vulnerability_occurrences_to_occurrences.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddReferenceFromVulnerabilityOccurrencesToOccurrences < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :vulnerability_occurrences, :vulnerability_id, :bigint
+ end
+end
diff --git a/db/migrate/20190929180827_add_foreign_keys_and_indexes_to_vulnerabilities.rb b/db/migrate/20190929180827_add_foreign_keys_and_indexes_to_vulnerabilities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4d7c2363083c583fa4d136efcd6c4db0fefbbc6c
--- /dev/null
+++ b/db/migrate/20190929180827_add_foreign_keys_and_indexes_to_vulnerabilities.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddForeignKeysAndIndexesToVulnerabilities < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :vulnerabilities, :milestone_id
+ add_concurrent_foreign_key :vulnerabilities, :milestones, column: :milestone_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :epic_id
+ add_concurrent_foreign_key :vulnerabilities, :epics, column: :epic_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :project_id
+ add_concurrent_foreign_key :vulnerabilities, :projects, column: :project_id
+
+ add_concurrent_index :vulnerabilities, :author_id
+ add_concurrent_foreign_key :vulnerabilities, :users, column: :author_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :updated_by_id
+ add_concurrent_foreign_key :vulnerabilities, :users, column: :updated_by_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :last_edited_by_id
+ add_concurrent_foreign_key :vulnerabilities, :users, column: :last_edited_by_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :closed_by_id
+ add_concurrent_foreign_key :vulnerabilities, :users, column: :closed_by_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :start_date_sourcing_milestone_id
+ add_concurrent_foreign_key :vulnerabilities, :milestones, column: :start_date_sourcing_milestone_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerabilities, :due_date_sourcing_milestone_id
+ add_concurrent_foreign_key :vulnerabilities, :milestones, column: :due_date_sourcing_milestone_id, on_delete: :nullify
+
+ add_concurrent_index :vulnerability_occurrences, :vulnerability_id
+ add_concurrent_foreign_key :vulnerability_occurrences, :vulnerabilities, column: :vulnerability_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :vulnerability_occurrences, :vulnerabilities
+ remove_concurrent_index :vulnerability_occurrences, :vulnerability_id
+
+ remove_foreign_key :vulnerabilities, column: :due_date_sourcing_milestone_id
+ remove_concurrent_index :vulnerabilities, :due_date_sourcing_milestone_id
+
+ remove_foreign_key :vulnerabilities, column: :start_date_sourcing_milestone_id
+ remove_concurrent_index :vulnerabilities, :start_date_sourcing_milestone_id
+
+ remove_foreign_key :vulnerabilities, column: :closed_by_id
+ remove_concurrent_index :vulnerabilities, :closed_by_id
+
+ remove_foreign_key :vulnerabilities, column: :last_edited_by_id
+ remove_concurrent_index :vulnerabilities, :last_edited_by_id
+
+ remove_foreign_key :vulnerabilities, column: :updated_by_id
+ remove_concurrent_index :vulnerabilities, :updated_by_id
+
+ remove_foreign_key :vulnerabilities, column: :author_id
+ remove_concurrent_index :vulnerabilities, :author_id
+
+ remove_foreign_key :vulnerabilities, :projects
+ remove_concurrent_index :vulnerabilities, :project_id
+
+ remove_foreign_key :vulnerabilities, :epics
+ remove_concurrent_index :vulnerabilities, :epic_id
+
+ remove_foreign_key :vulnerabilities, :milestones
+ remove_concurrent_index :vulnerabilities, :milestone_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4ca75ece3f9072acb56ce6f201ad5d202fe387b9..0664f83c01719c43a09f696c38cf68b5bbf7cee0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_09_27_074328) do
+ActiveRecord::Schema.define(version: 2019_09_29_180827) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -3704,6 +3704,42 @@
t.index ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true
end
+ create_table "vulnerabilities", force: :cascade do |t|
+ t.bigint "milestone_id"
+ t.bigint "epic_id"
+ t.bigint "project_id", null: false
+ t.bigint "author_id", null: false
+ t.bigint "updated_by_id"
+ t.bigint "last_edited_by_id"
+ t.date "start_date"
+ t.date "due_date"
+ t.datetime_with_timezone "last_edited_at"
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.string "title", limit: 255, null: false
+ t.text "title_html", null: false
+ t.text "description"
+ t.text "description_html"
+ t.bigint "start_date_sourcing_milestone_id"
+ t.bigint "due_date_sourcing_milestone_id"
+ t.bigint "closed_by_id"
+ t.datetime_with_timezone "closed_at"
+ t.integer "state", limit: 2, default: 1, null: false
+ t.integer "severity", limit: 2, null: false
+ t.boolean "severity_overridden", default: false
+ t.integer "confidence", limit: 2, null: false
+ t.boolean "confidence_overridden", default: false
+ t.index ["author_id"], name: "index_vulnerabilities_on_author_id"
+ t.index ["closed_by_id"], name: "index_vulnerabilities_on_closed_by_id"
+ t.index ["due_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_due_date_sourcing_milestone_id"
+ t.index ["epic_id"], name: "index_vulnerabilities_on_epic_id"
+ t.index ["last_edited_by_id"], name: "index_vulnerabilities_on_last_edited_by_id"
+ t.index ["milestone_id"], name: "index_vulnerabilities_on_milestone_id"
+ t.index ["project_id"], name: "index_vulnerabilities_on_project_id"
+ t.index ["start_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_start_date_sourcing_milestone_id"
+ t.index ["updated_by_id"], name: "index_vulnerabilities_on_updated_by_id"
+ end
+
create_table "vulnerability_feedback", id: :serial, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -3771,10 +3807,12 @@
t.string "name", null: false
t.string "metadata_version", null: false
t.text "raw_metadata", null: false
+ t.bigint "vulnerability_id"
t.index ["primary_identifier_id"], name: "index_vulnerability_occurrences_on_primary_identifier_id"
t.index ["project_id", "primary_identifier_id", "location_fingerprint", "scanner_id"], name: "index_vulnerability_occurrences_on_unique_keys", unique: true
t.index ["scanner_id"], name: "index_vulnerability_occurrences_on_scanner_id"
t.index ["uuid"], name: "index_vulnerability_occurrences_on_uuid", unique: true
+ t.index ["vulnerability_id"], name: "index_vulnerability_occurrences_on_vulnerability_id"
end
create_table "vulnerability_scanners", force: :cascade do |t|
@@ -4201,6 +4239,15 @@
add_foreign_key "users_ops_dashboard_projects", "projects", on_delete: :cascade
add_foreign_key "users_ops_dashboard_projects", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
+ add_foreign_key "vulnerabilities", "epics", name: "fk_1d37cddf91", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "milestones", column: "due_date_sourcing_milestone_id", name: "fk_7c5bb22a22", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "milestones", column: "start_date_sourcing_milestone_id", name: "fk_88b4d546ef", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "milestones", name: "fk_131d289c65", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "projects", name: "fk_efb96ab1e2", on_delete: :cascade
+ add_foreign_key "vulnerabilities", "users", column: "author_id", name: "fk_b1de915a15", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "users", column: "closed_by_id", name: "fk_cf5c60acbf", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "users", column: "last_edited_by_id", name: "fk_1302949740", on_delete: :nullify
+ add_foreign_key "vulnerabilities", "users", column: "updated_by_id", name: "fk_7ac31eacb9", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "ci_pipelines", column: "pipeline_id", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "issues", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "merge_requests", name: "fk_563ff1912e", on_delete: :nullify
@@ -4213,6 +4260,7 @@
add_foreign_key "vulnerability_occurrence_pipelines", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrence_pipelines", "vulnerability_occurrences", column: "occurrence_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrences", "projects", on_delete: :cascade
+ add_foreign_key "vulnerability_occurrences", "vulnerabilities", name: "fk_97ffe77653", on_delete: :nullify
add_foreign_key "vulnerability_occurrences", "vulnerability_identifiers", column: "primary_identifier_id", on_delete: :cascade
add_foreign_key "vulnerability_occurrences", "vulnerability_scanners", column: "scanner_id", on_delete: :cascade
add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade
diff --git a/doc/development/README.md b/doc/development/README.md
index 6480d40303b64004b958adef8578676c75de29d6..3aa65180fc9f3a9d637d049e0ac2e82c7d744880 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -34,6 +34,7 @@ description: 'Learn how to contribute to GitLab.'
## Backend guides
- [GitLab utilities](utilities.md)
+- [Issuable-like Rails models](issuable-like-models.md)
- [Logging](logging.md)
- [API styleguide](api_styleguide.md) Use this styleguide if you are
contributing to the API
diff --git a/doc/development/issuable-like-models.md b/doc/development/issuable-like-models.md
new file mode 100644
index 0000000000000000000000000000000000000000..27cac825b7f401ad91a7de1008faa19cc8020e28
--- /dev/null
+++ b/doc/development/issuable-like-models.md
@@ -0,0 +1,19 @@
+# Issuable-like Rails models utilities
+
+GitLab Rails codebase contains several models that hold common functionality and behave similarly to an [Issue]. Other
+examples of `Issuable`s are [Merge Requests] and [Epics].
+
+This guide accumulates guidelines on working with such Rails models.
+
+## Important text fields
+
+There are max length constraints for the most important text fields for `Issuable`s:
+
+- `title`: 255 chars
+- `title_html`: 800 chars
+- `description`: 16000 chars
+- `description_html`: 48000 chars
+
+[Issue]: https://docs.gitlab.com/ee/user/project/issues
+[Merge Requests]: https://docs.gitlab.com/ee/user/project/merge_requests
+[Epics]: https://docs.gitlab.com/ee/user/group/epics
diff --git a/ee/app/models/ee/ci/pipeline.rb b/ee/app/models/ee/ci/pipeline.rb
index d8ca9d15e347378b2667eb3c4fa8856fbb2e71f2..56ed1884c134bc1409e8f59d2d9120003e4d6d59 100644
--- a/ee/app/models/ee/ci/pipeline.rb
+++ b/ee/app/models/ee/ci/pipeline.rb
@@ -17,7 +17,7 @@ module Pipeline
has_many :job_artifacts, through: :builds
has_many :vulnerabilities_occurrence_pipelines, class_name: 'Vulnerabilities::OccurrencePipeline'
- has_many :vulnerabilities, source: :occurrence, through: :vulnerabilities_occurrence_pipelines, class_name: 'Vulnerabilities::Occurrence'
+ has_many :vulnerability_findings, source: :occurrence, through: :vulnerabilities_occurrence_pipelines, class_name: 'Vulnerabilities::Occurrence'
has_one :source_pipeline, class_name: "::Ci::Sources::Pipeline", inverse_of: :pipeline
has_many :sourced_pipelines, class_name: "::Ci::Sources::Pipeline", foreign_key: :source_pipeline_id
diff --git a/ee/app/models/ee/project.rb b/ee/app/models/ee/project.rb
index dd196b4d092666561078cffdbc8c3ccc8a5f22dc..9f6702912377ae4de6c55aebf442d22c780597a6 100644
--- a/ee/app/models/ee/project.rb
+++ b/ee/app/models/ee/project.rb
@@ -61,10 +61,15 @@ module Project
has_many :audit_events, as: :entity
has_many :designs, inverse_of: :project, class_name: 'DesignManagement::Design'
has_many :path_locks
+
+ # the rationale behind vulnerabilities and vulnerability_findings can be found here:
+ # https://gitlab.com/gitlab-org/gitlab/issues/10252#terminology
+ has_many :vulnerabilities
has_many :vulnerability_feedback, class_name: 'Vulnerabilities::Feedback'
- has_many :vulnerabilities, class_name: 'Vulnerabilities::Occurrence'
+ has_many :vulnerability_findings, class_name: 'Vulnerabilities::Occurrence'
has_many :vulnerability_identifiers, class_name: 'Vulnerabilities::Identifier'
has_many :vulnerability_scanners, class_name: 'Vulnerabilities::Scanner'
+
has_many :protected_environments
has_many :software_license_policies, inverse_of: :project, class_name: 'SoftwareLicensePolicy'
accepts_nested_attributes_for :software_license_policies, allow_destroy: true
diff --git a/ee/app/models/vulnerability.rb b/ee/app/models/vulnerability.rb
new file mode 100644
index 0000000000000000000000000000000000000000..088ecc04ebdc1bc5c482650a8331caaa4cc652ca
--- /dev/null
+++ b/ee/app/models/vulnerability.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class Vulnerability < ApplicationRecord
+ belongs_to :project
+ belongs_to :milestone
+ belongs_to :epic
+
+ belongs_to :author, class_name: 'User'
+ belongs_to :updated_by, class_name: 'User'
+ belongs_to :last_edited_by, class_name: 'User'
+ belongs_to :closed_by, class_name: 'User'
+
+ enum state: { opened: 1, closed: 2 }
+ enum severity: Vulnerabilities::Occurrence::SEVERITY_LEVELS, _prefix: :severity
+ enum confidence: Vulnerabilities::Occurrence::CONFIDENCE_LEVELS, _prefix: :confidence
+
+ validates :project, :author, :title, :title_html, :severity, :confidence, presence: true
+
+ # at this stage Vulnerability is not an Issuable, has some important attributes (and their constraints) in common
+ validates :title, length: { maximum: Issuable::TITLE_LENGTH_MAX }
+ validates :title_html, length: { maximum: Issuable::TITLE_HTML_LENGTH_MAX }, allow_blank: true
+ validates :description, length: { maximum: Issuable::DESCRIPTION_LENGTH_MAX }, allow_blank: true
+ validates :description_html, length: { maximum: Issuable::DESCRIPTION_HTML_LENGTH_MAX }, allow_blank: true
+end
diff --git a/ee/app/services/security/store_report_service.rb b/ee/app/services/security/store_report_service.rb
index 7c81975e8f1db34d60839084a2d15ba3039e58a5..4b9cbe0ef25a246745554cf7d928944f83205bd3 100644
--- a/ee/app/services/security/store_report_service.rb
+++ b/ee/app/services/security/store_report_service.rb
@@ -26,17 +26,17 @@ def execute
private
def executed?
- pipeline.vulnerabilities.report_type(@report.type).any?
+ pipeline.vulnerability_findings.report_type(@report.type).any?
end
def create_all_vulnerabilities!
@report.occurrences.each do |occurrence|
- create_vulnerability(occurrence)
+ create_vulnerability_finding(occurrence)
end
end
- def create_vulnerability(occurrence)
- vulnerability = create_or_find_vulnerability_object(occurrence)
+ def create_vulnerability_finding(occurrence)
+ vulnerability = create_or_find_vulnerability_finding(occurrence)
occurrence.identifiers.map do |identifier|
create_vulnerability_identifier_object(vulnerability, identifier)
@@ -46,7 +46,7 @@ def create_vulnerability(occurrence)
end
# rubocop: disable CodeReuse/ActiveRecord
- def create_or_find_vulnerability_object(occurrence)
+ def create_or_find_vulnerability_finding(occurrence)
find_params = {
scanner: scanners_objects[occurrence.scanner.key],
primary_identifier: identifiers_objects[occurrence.primary_identifier.key],
@@ -57,11 +57,12 @@ def create_or_find_vulnerability_object(occurrence)
.except(:compare_key, :identifiers, :location, :scanner)
begin
- project.vulnerabilities
+ project
+ .vulnerability_findings
.create_with(create_params)
.find_or_create_by!(find_params)
rescue ActiveRecord::RecordNotUnique
- project.vulnerabilities.find_by!(find_params)
+ project.vulnerability_findings.find_by!(find_params)
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/ee/db/fixtures/development/20_vulnerabilities.rb b/ee/db/fixtures/development/20_vulnerabilities.rb
index d699dcf3ed5b817dfd95ea2ee9393b0cdee3e366..f1f678b97a963aee5b4406eb42bb49c46492840b 100644
--- a/ee/db/fixtures/development/20_vulnerabilities.rb
+++ b/ee/db/fixtures/development/20_vulnerabilities.rb
@@ -35,7 +35,7 @@ def seed!
private
def create_occurrence(rank, primary_identifier)
- project.vulnerabilities.create!(
+ project.vulnerability_findings.create!(
uuid: random_uuid,
name: 'Cipher with no integrity',
report_type: :sast,
diff --git a/ee/spec/factories/vulnerabilities.rb b/ee/spec/factories/vulnerabilities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7c1bb96825ac61863fdbd96d94315fcb9bf8872e
--- /dev/null
+++ b/ee/spec/factories/vulnerabilities.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :vulnerability do
+ project
+ author
+ title { generate(:title) }
+ title_html { "
#{title}
" }
+ severity { :high }
+ confidence { :medium }
+
+ trait :opened do
+ state { :opened }
+ end
+
+ trait :closed do
+ state { :closed }
+ closed_at { Time.now }
+ end
+ end
+end
diff --git a/ee/spec/models/ci/pipeline_spec.rb b/ee/spec/models/ci/pipeline_spec.rb
index df66074459285c39246251638ffe295a462372d5..c1213392e6e219202af466d9657917a0a0f4262b 100644
--- a/ee/spec/models/ci/pipeline_spec.rb
+++ b/ee/spec/models/ci/pipeline_spec.rb
@@ -16,7 +16,7 @@
it { is_expected.to have_many(:triggered_pipelines) }
it { is_expected.to have_many(:downstream_bridges) }
it { is_expected.to have_many(:job_artifacts).through(:builds) }
- it { is_expected.to have_many(:vulnerabilities).through(:vulnerabilities_occurrence_pipelines).class_name('Vulnerabilities::Occurrence') }
+ it { is_expected.to have_many(:vulnerability_findings).through(:vulnerabilities_occurrence_pipelines).class_name('Vulnerabilities::Occurrence') }
it { is_expected.to have_many(:vulnerabilities_occurrence_pipelines).class_name('Vulnerabilities::OccurrencePipeline') }
describe '.failure_reasons' do
diff --git a/ee/spec/models/concerns/ee/issuable_spec.rb b/ee/spec/models/concerns/ee/issuable_spec.rb
index 61cb8540d93dc415451b7ad8ff6d80254728a10f..9df063da35d09b0eaf788a4e612ce353ec6aef6f 100644
--- a/ee/spec/models/concerns/ee/issuable_spec.rb
+++ b/ee/spec/models/concerns/ee/issuable_spec.rb
@@ -15,7 +15,10 @@
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_most(255) }
- it { is_expected.to validate_length_of(:description).is_at_most(1_000_000) }
+ it { is_expected.to validate_length_of(:description).is_at_most(16_000).on(:create) }
+
+ it_behaves_like 'validates description length with custom validation'
+ it_behaves_like 'truncates the description to its allowed maximum length on import'
end
end
diff --git a/ee/spec/models/vulnerability_spec.rb b/ee/spec/models/vulnerability_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49a2c0783c68cbfb8358eb967ce62607dc006ff4
--- /dev/null
+++ b/ee/spec/models/vulnerability_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Vulnerability do
+ let(:severity_values) do
+ { undefined: 0,
+ info: 1,
+ unknown: 2,
+ low: 4,
+ medium: 5,
+ high: 6,
+ critical: 7 }
+ end
+ let(:confidence_values) do
+ { undefined: 0,
+ ignore: 1,
+ unknown: 2,
+ experimental: 3,
+ low: 4,
+ medium: 5,
+ high: 6,
+ confirmed: 7 }
+ end
+
+ it { is_expected.to define_enum_for(:state) }
+ it { is_expected.to define_enum_for(:severity).with_values(severity_values).with_prefix(:severity) }
+ it { is_expected.to define_enum_for(:confidence).with_values(confidence_values).with_prefix(:confidence) }
+
+ describe 'associations' do
+ subject { build(:vulnerability) }
+
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:milestone) }
+ it { is_expected.to belong_to(:epic) }
+
+ it { is_expected.to belong_to(:author).class_name('User') }
+ it { is_expected.to belong_to(:updated_by).class_name('User') }
+ it { is_expected.to belong_to(:last_edited_by).class_name('User') }
+ it { is_expected.to belong_to(:closed_by).class_name('User') }
+ end
+
+ describe 'validations' do
+ subject { build(:vulnerability) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:author) }
+ it { is_expected.to validate_presence_of(:title) }
+ it { is_expected.to validate_presence_of(:title_html) }
+ it { is_expected.to validate_presence_of(:severity) }
+ it { is_expected.to validate_presence_of(:confidence) }
+
+ it { is_expected.to validate_length_of(:title).is_at_most(255) }
+ it { is_expected.to validate_length_of(:title_html).is_at_most(800) }
+ it { is_expected.to validate_length_of(:description).is_at_most(16_000).allow_nil }
+ it { is_expected.to validate_length_of(:description_html).is_at_most(48_000).allow_nil }
+ end
+end
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index b2a31ff2275c74df941bd4d9edaf3474c2960a1a..4520d1bb2da57e8e6e2be0cbd41f536008febbb9 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -56,18 +56,6 @@
graph LR
A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;
```
- ```mermaid
- graph LR
- A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;
- ```
- ```mermaid
- graph LR
- A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;
- ```
- ```mermaid
- graph LR
- A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;B-->A;A-->B;
- ```
MERMAID
project = create(:project, :public)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 36cac12e6bd463198204efc9424f69639680b25d..50abd4b23e3d24d6526eb7ef3dc72dfe894a1061 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -172,7 +172,7 @@ ci_pipelines:
- downstream_bridges
- job_artifacts
- vulnerabilities_occurrence_pipelines
-- vulnerabilities
+- vulnerability_findings
pipeline_variables:
- pipeline
stages:
@@ -389,6 +389,7 @@ project:
- sourced_pipelines
- prometheus_metrics
- vulnerabilities
+- vulnerability_findings
- vulnerability_feedback
- vulnerability_identifiers
- vulnerability_scanners
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 65d41edc035982d3d067342842cdbac1ac246095..e64af9206f6444439273979cc6e383fc10ecb934 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -46,7 +46,10 @@
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_at_most(255) }
- it { is_expected.to validate_length_of(:description).is_at_most(1_000_000) }
+ it { is_expected.to validate_length_of(:description).is_at_most(16_000).on(:create) }
+
+ it_behaves_like 'validates description length with custom validation'
+ it_behaves_like 'truncates the description to its allowed maximum length on import'
end
describe 'milestone' do
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
index 9604555c57d84bdc7228fbe7e25d0dc77fdd0611..13ab29e2ca6df38b949657c7ffc562e3d12b528d 100644
--- a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'matches_cross_reference_regex? fails fast' do
it 'fails fast for long strings' do
# took well under 1 second in CI https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/3267#note_172823
@@ -6,3 +8,59 @@
end.not_to raise_error
end
end
+
+shared_examples_for 'validates description length with custom validation' do
+ let(:issuable) { build(:issue, description: 'x' * 16_001) }
+ let(:context) { :update }
+
+ subject { issuable.validate(context) }
+
+ context 'when Issuable is a new record' do
+ it 'validates the maximum description length' do
+ subject
+ expect(issuable.errors[:description]).to eq(["is too long (maximum is 16000 characters)"])
+ end
+
+ context 'on create' do
+ let(:context) { :create }
+
+ it 'does not validate the maximum description length' do
+ allow(issuable).to receive(:description_max_length_for_new_records_is_valid).and_call_original
+
+ subject
+
+ expect(issuable).not_to have_received(:description_max_length_for_new_records_is_valid)
+ end
+ end
+ end
+
+ context 'when Issuable is an existing record' do
+ before do
+ allow(issuable).to receive(:expire_etag_cache) # to skip the expire_etag_cache callback
+
+ issuable.save!(validate: false)
+ end
+
+ it 'does not validate the maximum description length' do
+ subject
+ expect(issuable.errors).not_to have_key(:description)
+ end
+ end
+end
+
+shared_examples_for 'truncates the description to its allowed maximum length on import' do
+ before do
+ allow(issuable).to receive(:importing?).and_return(true)
+ end
+
+ let(:issuable) { build(:issue, description: 'x' * 16_001) }
+
+ subject { issuable.validate(:create) }
+
+ it 'truncates the description to its allowed maximum length' do
+ subject
+
+ expect(issuable.description).to eq('x' * 16_000)
+ expect(issuable.errors[:description]).to be_empty
+ end
+end