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