Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-05 03:10:41 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-05 03:10:41 +0300
commit415153114ac36a0e25b6beb6f0543581bcedc54c (patch)
tree7766607058528a92efb52f05fe03005a9392a0ea
parent4f49d2c8cd9b0c54c1055480df5cde2e13d7c76d (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/controllers/concerns/boards_actions.rb1
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/models/concerns/prometheus_adapter.rb2
-rw-r--r--app/models/user.rb6
-rw-r--r--app/models/users/credit_card_validation.rb9
-rw-r--r--app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json7
-rw-r--r--changelogs/unreleased/202423-validate-foreign-key-on-group-hooks.yml5
-rw-r--r--changelogs/unreleased/328066-semgrep-config-ui.yml5
-rw-r--r--changelogs/unreleased/migrate-create-table-user-credit-card-validations.yml5
-rw-r--r--changelogs/unreleased/populate-timelogs-project_id.yml5
-rw-r--r--db/migrate/20210428151144_update_invalid_web_hooks.rb24
-rw-r--r--db/migrate/20210428151238_validate_foreign_key_on_group_hooks.rb15
-rw-r--r--db/migrate/20210429131525_create_user_credit_card_validations.rb22
-rw-r--r--db/post_migrate/20210427212034_schedule_update_timelogs_project_id.rb32
-rw-r--r--db/schema_migrations/202104272120341
-rw-r--r--db/schema_migrations/202104281511441
-rw-r--r--db/schema_migrations/202104281512381
-rw-r--r--db/schema_migrations/202104291315251
-rw-r--r--db/structure.sql13
-rw-r--r--doc/development/usage_ping/index.md5
-rw-r--r--lib/gitlab/background_migration/update_timelogs_project_id.rb44
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml2
-rw-r--r--lib/security/ci_configuration/sast_build_action.rb2
-rw-r--r--spec/factories/ci/job_artifacts.rb10
-rw-r--r--spec/factories/users.rb6
-rw-r--r--spec/factories/users/credit_card_validations.rb9
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-minimal.json68
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report.json4
-rw-r--r--spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js2
-rw-r--r--spec/frontend/api_spec.js4
-rw-r--r--spec/frontend/code_navigation/components/app_spec.js2
-rw-r--r--spec/frontend/code_navigation/store/mutations_spec.js4
-rw-r--r--spec/frontend/commits_spec.js4
-rw-r--r--spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap2
-rw-r--r--spec/frontend/contributors/component/contributors_spec.js2
-rw-r--r--spec/frontend/diffs/mock_data/diff_metadata.js2
-rw-r--r--spec/frontend/notes/old_notes_spec.js2
-rw-r--r--spec/frontend/ref/stores/mutations_spec.js4
-rw-r--r--spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js4
-rw-r--r--spec/frontend/reports/grouped_test_report/store/mutations_spec.js2
-rw-r--r--spec/frontend/reports/mock_data/mock_data.js2
-rw-r--r--spec/frontend/reports/mock_data/recent_failures_report.json4
-rw-r--r--spec/helpers/learn_gitlab_helper_spec.rb16
-rw-r--r--spec/lib/gitlab/background_migration/update_timelogs_project_id_spec.rb52
-rw-r--r--spec/migrations/schedule_update_timelogs_project_id_spec.rb33
-rw-r--r--spec/migrations/update_invalid_web_hooks_spec.rb30
-rw-r--r--spec/models/user_spec.rb41
-rw-r--r--spec/models/users/credit_card_validation_spec.rb7
49 files changed, 491 insertions, 35 deletions
diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb
index 7bffe1e1141..8c2b0300589 100644
--- a/app/controllers/concerns/boards_actions.rb
+++ b/app/controllers/concerns/boards_actions.rb
@@ -7,6 +7,7 @@ module BoardsActions
included do
include BoardsResponses
+ before_action :authorize_read_board!, only: [:index, :show]
before_action :boards, only: :index
before_action :board, only: :show
before_action :push_licensed_features, only: [:index, :show]
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index be38fe25842..e1f09d73739 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -5,7 +5,6 @@ class Groups::BoardsController < Groups::ApplicationController
include RecordUserLastActivity
include Gitlab::Utils::StrongMemoize
- before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:graphql_board_lists, group, default_enabled: false)
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 349649c7b35..9a3e9437426 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -5,7 +5,6 @@ class Projects::BoardsController < Projects::ApplicationController
include IssuableCollections
before_action :check_issues_available!
- before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
before_action do
push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index b26d376bb9b..afebc426762 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -33,7 +33,7 @@ module PrometheusAdapter
# This is a light-weight check if a prometheus client is properly configured.
def configured?
- raise NotImplemented
+ raise NotImplementedError
end
# This is a heavy-weight check if a prometheus is properly configured and accessible from GitLab.
diff --git a/app/models/user.rb b/app/models/user.rb
index 92cbf863d63..581a1bd893b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -205,6 +205,7 @@ class User < ApplicationRecord
has_one :user_detail
has_one :user_highest_role
has_one :user_canonical_email
+ has_one :credit_card_validation, class_name: '::Users::CreditCardValidation'
has_one :atlassian_identity, class_name: 'Atlassian::Identity'
has_many :reviews, foreign_key: :author_id, inverse_of: :author
@@ -317,6 +318,7 @@ class User < ApplicationRecord
accepts_nested_attributes_for :user_preference, update_only: true
accepts_nested_attributes_for :user_detail, update_only: true
+ accepts_nested_attributes_for :credit_card_validation, update_only: true
state_machine :state, initial: :active do
event :block do
@@ -1223,6 +1225,10 @@ class User < ApplicationRecord
user_highest_role&.highest_access_level || Gitlab::Access::NO_ACCESS
end
+ def credit_card_validated_at
+ credit_card_validation&.credit_card_validated_at
+ end
+
def accessible_deploy_keys
DeployKey.from_union([
DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)),
diff --git a/app/models/users/credit_card_validation.rb b/app/models/users/credit_card_validation.rb
new file mode 100644
index 00000000000..2ccebc65552
--- /dev/null
+++ b/app/models/users/credit_card_validation.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Users
+ class CreditCardValidation < ApplicationRecord
+ self.table_name = 'user_credit_card_validations'
+
+ belongs_to :user
+ end
+end
diff --git a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
index dc4880946b2..7c3720dd2e6 100644
--- a/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
+++ b/app/validators/json_schemas/security_ci_configuration_schemas/sast_ui_schema.json
@@ -161,6 +161,13 @@
"variables": []
},
{
+ "name": "semgrep",
+ "label": "Semgrep",
+ "enabled": true,
+ "description": "Multi-language scanning",
+ "variables": []
+ },
+ {
"name": "sobelow",
"label": "Sobelow",
"enabled" : true,
diff --git a/changelogs/unreleased/202423-validate-foreign-key-on-group-hooks.yml b/changelogs/unreleased/202423-validate-foreign-key-on-group-hooks.yml
new file mode 100644
index 00000000000..bf39cf13dd4
--- /dev/null
+++ b/changelogs/unreleased/202423-validate-foreign-key-on-group-hooks.yml
@@ -0,0 +1,5 @@
+---
+title: Validate foreign key on GroupHooks
+merge_request: 60527
+author:
+type: other
diff --git a/changelogs/unreleased/328066-semgrep-config-ui.yml b/changelogs/unreleased/328066-semgrep-config-ui.yml
new file mode 100644
index 00000000000..e1a592c9d0c
--- /dev/null
+++ b/changelogs/unreleased/328066-semgrep-config-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Add semgrep to SAST config UI
+merge_request: 60460
+author:
+type: added
diff --git a/changelogs/unreleased/migrate-create-table-user-credit-card-validations.yml b/changelogs/unreleased/migrate-create-table-user-credit-card-validations.yml
new file mode 100644
index 00000000000..cabaea5159c
--- /dev/null
+++ b/changelogs/unreleased/migrate-create-table-user-credit-card-validations.yml
@@ -0,0 +1,5 @@
+---
+title: Create table user_credit_card_validations
+merge_request: 60626
+author:
+type: changed
diff --git a/changelogs/unreleased/populate-timelogs-project_id.yml b/changelogs/unreleased/populate-timelogs-project_id.yml
new file mode 100644
index 00000000000..db133120b64
--- /dev/null
+++ b/changelogs/unreleased/populate-timelogs-project_id.yml
@@ -0,0 +1,5 @@
+---
+title: Populate timelogs.project_id
+merge_request: 60439
+author: Lee Tickett @leetickett
+type: added
diff --git a/db/migrate/20210428151144_update_invalid_web_hooks.rb b/db/migrate/20210428151144_update_invalid_web_hooks.rb
new file mode 100644
index 00000000000..4f45e7aaa9b
--- /dev/null
+++ b/db/migrate/20210428151144_update_invalid_web_hooks.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class UpdateInvalidWebHooks < ActiveRecord::Migration[6.0]
+ disable_ddl_transaction!
+
+ class WebHook < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'web_hooks'
+ end
+
+ def up
+ WebHook.each_batch(of: 10_000, column: :id) do |relation|
+ relation.where(type: 'ProjectHook')
+ .where.not(project_id: nil)
+ .where.not(group_id: nil)
+ .update_all(group_id: nil)
+ end
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20210428151238_validate_foreign_key_on_group_hooks.rb b/db/migrate/20210428151238_validate_foreign_key_on_group_hooks.rb
new file mode 100644
index 00000000000..e7020665b25
--- /dev/null
+++ b/db/migrate/20210428151238_validate_foreign_key_on_group_hooks.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ValidateForeignKeyOnGroupHooks < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ CONSTRAINT_NAME = 'fk_rails_d35697648e'
+
+ def up
+ validate_foreign_key :web_hooks, :group_id, name: CONSTRAINT_NAME
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20210429131525_create_user_credit_card_validations.rb b/db/migrate/20210429131525_create_user_credit_card_validations.rb
new file mode 100644
index 00000000000..8548274b29a
--- /dev/null
+++ b/db/migrate/20210429131525_create_user_credit_card_validations.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class CreateUserCreditCardValidations < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ create_table :user_credit_card_validations, id: false do |t|
+ t.references :user, foreign_key: { on_delete: :cascade }, index: false, primary_key: true, default: nil
+ t.datetime_with_timezone :credit_card_validated_at, null: false
+ end
+ end
+ end
+
+ def down
+ with_lock_retries do
+ drop_table :user_credit_card_validations
+ end
+ end
+end
diff --git a/db/post_migrate/20210427212034_schedule_update_timelogs_project_id.rb b/db/post_migrate/20210427212034_schedule_update_timelogs_project_id.rb
new file mode 100644
index 00000000000..13b802f7cb4
--- /dev/null
+++ b/db/post_migrate/20210427212034_schedule_update_timelogs_project_id.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class ScheduleUpdateTimelogsProjectId < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ BATCH_SIZE = 50_000
+ DELAY_INTERVAL = 2.minutes
+ MIGRATION = 'UpdateTimelogsProjectId'
+
+ disable_ddl_transaction!
+
+ class Timelog < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'timelogs'
+ self.inheritance_column = :_type_disabled
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ Timelog.all,
+ MIGRATION,
+ DELAY_INTERVAL,
+ batch_size: BATCH_SIZE
+ )
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/schema_migrations/20210427212034 b/db/schema_migrations/20210427212034
new file mode 100644
index 00000000000..4954b5722a9
--- /dev/null
+++ b/db/schema_migrations/20210427212034
@@ -0,0 +1 @@
+2ffe65c4abcb8f638198943e1b74de710387438fb7c93addb05ccb3e86729934 \ No newline at end of file
diff --git a/db/schema_migrations/20210428151144 b/db/schema_migrations/20210428151144
new file mode 100644
index 00000000000..53771dd9401
--- /dev/null
+++ b/db/schema_migrations/20210428151144
@@ -0,0 +1 @@
+468373a97f7bd66197c81f01bebd27256cf96ec8fc226c5d73e579a7ecc3930d \ No newline at end of file
diff --git a/db/schema_migrations/20210428151238 b/db/schema_migrations/20210428151238
new file mode 100644
index 00000000000..3fa6ecefc42
--- /dev/null
+++ b/db/schema_migrations/20210428151238
@@ -0,0 +1 @@
+3244023441c2afa450ad76345a494975b4a7154892298daf1ec4223d27fb7ca3 \ No newline at end of file
diff --git a/db/schema_migrations/20210429131525 b/db/schema_migrations/20210429131525
new file mode 100644
index 00000000000..4ab6b84eea9
--- /dev/null
+++ b/db/schema_migrations/20210429131525
@@ -0,0 +1 @@
+68ac54fa7b4e4ef99e58c31d8f960b6f986fd679c11ead235704c7a75b4617ac \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index b237a7f526c..fce0a085dac 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18282,6 +18282,11 @@ CREATE SEQUENCE user_canonical_emails_id_seq
ALTER SEQUENCE user_canonical_emails_id_seq OWNED BY user_canonical_emails.id;
+CREATE TABLE user_credit_card_validations (
+ user_id bigint NOT NULL,
+ credit_card_validated_at timestamp with time zone NOT NULL
+);
+
CREATE TABLE user_custom_attributes (
id integer NOT NULL,
created_at timestamp without time zone NOT NULL,
@@ -21655,6 +21660,9 @@ ALTER TABLE ONLY user_callouts
ALTER TABLE ONLY user_canonical_emails
ADD CONSTRAINT user_canonical_emails_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY user_credit_card_validations
+ ADD CONSTRAINT user_credit_card_validations_pkey PRIMARY KEY (user_id);
+
ALTER TABLE ONLY user_custom_attributes
ADD CONSTRAINT user_custom_attributes_pkey PRIMARY KEY (id);
@@ -25941,6 +25949,9 @@ ALTER TABLE ONLY lfs_file_locks
ALTER TABLE ONLY project_alerting_settings
ADD CONSTRAINT fk_rails_27a84b407d FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY user_credit_card_validations
+ ADD CONSTRAINT fk_rails_27ebc03cbf FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY dast_site_validations
ADD CONSTRAINT fk_rails_285c617324 FOREIGN KEY (dast_site_token_id) REFERENCES dast_site_tokens(id) ON DELETE CASCADE;
@@ -26923,7 +26934,7 @@ ALTER TABLE ONLY pool_repositories
ADD CONSTRAINT fk_rails_d2711daad4 FOREIGN KEY (source_project_id) REFERENCES projects(id) ON DELETE SET NULL;
ALTER TABLE ONLY web_hooks
- ADD CONSTRAINT fk_rails_d35697648e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE NOT VALID;
+ ADD CONSTRAINT fk_rails_d35697648e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY group_group_links
ADD CONSTRAINT fk_rails_d3a0488427 FOREIGN KEY (shared_group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
diff --git a/doc/development/usage_ping/index.md b/doc/development/usage_ping/index.md
index 0e12c620a8a..e79cd127d5a 100644
--- a/doc/development/usage_ping/index.md
+++ b/doc/development/usage_ping/index.md
@@ -635,11 +635,12 @@ Implemented using Redis methods [PFADD](https://redis.io/commands/pfadd) and [PF
- `category`: event category. Used for getting total counts for events in a category, for easier
access to a group of events.
- - `redis_slot`: optional Redis slot; default value: event name. Used if needed to calculate totals
- for a group of metrics. Ensure keys are in the same slot. For example:
+ - `redis_slot`: optional Redis slot. Default value: event name. Only event data that is stored in the same slot
+ can be aggregated. Ensure keys are in the same slot. For example:
`users_creating_epics` with `redis_slot: 'users'` builds Redis key
`{users}_creating_epics-2020-34`. If `redis_slot` is not defined the Redis key will
be `{users_creating_epics}-2020-34`.
+ Recommended slots to use are: `users`, `projects`. This is the value we count.
- `expiry`: expiry time in days. Default: 29 days for daily aggregation and 6 weeks for weekly
aggregation.
- `aggregation`: may be set to a `:daily` or `:weekly` key. Defines how counting data is stored in Redis.
diff --git a/lib/gitlab/background_migration/update_timelogs_project_id.rb b/lib/gitlab/background_migration/update_timelogs_project_id.rb
new file mode 100644
index 00000000000..24c9967b88e
--- /dev/null
+++ b/lib/gitlab/background_migration/update_timelogs_project_id.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Class to populate project_id for timelogs
+ class UpdateTimelogsProjectId
+ BATCH_SIZE = 1000
+
+ def perform(start_id, stop_id)
+ (start_id..stop_id).step(BATCH_SIZE).each do |offset|
+ update_issue_timelogs(offset, offset + BATCH_SIZE)
+ update_merge_request_timelogs(offset, offset + BATCH_SIZE)
+ end
+ end
+
+ def update_issue_timelogs(batch_start, batch_stop)
+ execute(<<~SQL)
+ UPDATE timelogs
+ SET project_id = issues.project_id
+ FROM issues
+ WHERE issues.id = timelogs.issue_id
+ AND timelogs.id BETWEEN #{batch_start} AND #{batch_stop}
+ AND timelogs.project_id IS NULL;
+ SQL
+ end
+
+ def update_merge_request_timelogs(batch_start, batch_stop)
+ execute(<<~SQL)
+ UPDATE timelogs
+ SET project_id = merge_requests.target_project_id
+ FROM merge_requests
+ WHERE merge_requests.id = timelogs.merge_request_id
+ AND timelogs.id BETWEEN #{batch_start} AND #{batch_stop}
+ AND timelogs.project_id IS NULL;
+ SQL
+ end
+
+ def execute(sql)
+ @connection ||= ::ActiveRecord::Base.connection
+ @connection.execute(sql)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 3ebccfbba4a..b6f98d5034e 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -292,7 +292,7 @@ semgrep-sast:
# SAST_ANALYZER_IMAGE is an undocumented variable used internally to allow QA to
# override the analyzer image with a custom value. This may be subject to change or
# breakage across GitLab releases.
- SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/semgrep:latest"
+ SAST_ANALYZER_IMAGE: "$SECURE_ANALYZERS_PREFIX/semgrep:$SAST_ANALYZER_IMAGE_TAG"
rules:
- if: $SAST_DISABLED
when: never
diff --git a/lib/security/ci_configuration/sast_build_action.rb b/lib/security/ci_configuration/sast_build_action.rb
index c319fc1637d..d670d5054ae 100644
--- a/lib/security/ci_configuration/sast_build_action.rb
+++ b/lib/security/ci_configuration/sast_build_action.rb
@@ -3,7 +3,7 @@
module Security
module CiConfiguration
class SastBuildAction < BaseBuildAction
- SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, sobelow, spotbugs'
+ SAST_DEFAULT_ANALYZERS = 'bandit, brakeman, eslint, flawfinder, gosec, kubesec, nodejs-scan, phpcs-security-audit, pmd-apex, security-code-scan, semgrep, sobelow, spotbugs'
def initialize(auto_devops_enabled, params, existing_gitlab_ci_content)
super(auto_devops_enabled, existing_gitlab_ci_content)
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index b1adbcd283b..17cd495e217 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -277,6 +277,16 @@ FactoryBot.define do
end
end
+ trait :sast_minimal do
+ file_type { :sast }
+ file_format { :raw }
+
+ after(:build) do |artifact, _|
+ artifact.file = fixture_file_upload(
+ Rails.root.join('spec/fixtures/security_reports/master/gl-sast-report-minimal.json'), 'application/json')
+ end
+ end
+
trait :secret_detection do
file_type { :secret_detection }
file_format { :raw }
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 9b5e4a981a0..7ac44d55687 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -80,6 +80,12 @@ FactoryBot.define do
last_sign_in_ip { '127.0.0.1' }
end
+ trait :with_credit_card_validation do
+ after :create do |user|
+ create :credit_card_validation, user: user
+ end
+ end
+
trait :two_factor_via_otp do
before(:create) do |user|
user.otp_required_for_login = true
diff --git a/spec/factories/users/credit_card_validations.rb b/spec/factories/users/credit_card_validations.rb
new file mode 100644
index 00000000000..09940347708
--- /dev/null
+++ b/spec/factories/users/credit_card_validations.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :credit_card_validation, class: 'Users::CreditCardValidation' do
+ user
+
+ credit_card_validated_at { Time.current }
+ end
+end
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-minimal.json b/spec/fixtures/security_reports/master/gl-sast-report-minimal.json
new file mode 100644
index 00000000000..60a67453c9b
--- /dev/null
+++ b/spec/fixtures/security_reports/master/gl-sast-report-minimal.json
@@ -0,0 +1,68 @@
+{
+ "version": "14.0.0",
+ "vulnerabilities": [
+ {
+ "category": "sast",
+ "name": "Cipher with no integrity",
+ "message": "Cipher with no integrity",
+ "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
+ "severity": "Medium",
+ "confidence": "High",
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs"
+ },
+ "location": {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "class": "com.gitlab.security_products.tests.App",
+ "method": "insecureCypher"
+ },
+ "identifiers": [
+ {
+ "type": "find_sec_bugs_type",
+ "name": "Find Security Bugs-CIPHER_INTEGRITY",
+ "value": "CIPHER_INTEGRITY",
+ "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
+ }
+ ],
+ "tracking": {
+ "type": "source",
+ "items": [
+ {
+ "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
+ "start_line": 29,
+ "end_line": 29,
+ "signatures": [
+ {
+ "algorithm": "hash",
+ "value": "HASHVALUE"
+ },
+ {
+ "algorithm": "scope_offset",
+ "value": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:App[0]:insecureCypher[0]:2"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ],
+ "remediations": [],
+ "scan": {
+ "scanner": {
+ "id": "find_sec_bugs",
+ "name": "Find Security Bugs",
+ "url": "https://spotbugs.github.io",
+ "vendor": {
+ "name": "GitLab"
+ },
+ "version": "4.0.2"
+ },
+ "type": "sast",
+ "status": "success",
+ "start_time": "placeholder-value",
+ "end_time": "placeholder-value"
+ }
+}
diff --git a/spec/fixtures/security_reports/master/gl-sast-report.json b/spec/fixtures/security_reports/master/gl-sast-report.json
index 9da9fdc3832..3323c1fffe3 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report.json
@@ -154,8 +154,8 @@
"items": [
{
"file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
+ "start_line": 29,
+ "end_line": 29,
"signatures": [
{
"algorithm": "hash",
diff --git a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
index d32e582e498..2832de98769 100644
--- a/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
+++ b/spec/frontend/add_context_commits_modal/components/add_context_commits_modal_spec.js
@@ -40,7 +40,7 @@ describe('AddContextCommitsModal', () => {
store,
propsData: {
contextCommitsPath: '',
- targetBranch: 'master',
+ targetBranch: 'main',
mergeRequestIid: 1,
projectId: 1,
...props,
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index cb29dab86bf..139128e6d4a 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -930,7 +930,7 @@ describe('Api', () => {
describe('createBranch', () => {
it('creates new branch', (done) => {
- const ref = 'master';
+ const ref = 'main';
const branch = 'new-branch-name';
const dummyProjectPath = 'gitlab-org/gitlab-ce';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${encodeURIComponent(
@@ -1262,7 +1262,7 @@ describe('Api', () => {
)}/merge_requests`;
const options = {
source_branch: 'feature',
- target_branch: 'master',
+ target_branch: 'main',
title: 'Add feature',
};
diff --git a/spec/frontend/code_navigation/components/app_spec.js b/spec/frontend/code_navigation/components/app_spec.js
index ea389fa35c0..798f3bc0ee2 100644
--- a/spec/frontend/code_navigation/components/app_spec.js
+++ b/spec/frontend/code_navigation/components/app_spec.js
@@ -16,7 +16,7 @@ function factory(initialState = {}) {
state: {
...createState(),
...initialState,
- definitionPathPrefix: 'https://test.com/blob/master',
+ definitionPathPrefix: 'https://test.com/blob/main',
},
actions: {
fetchData,
diff --git a/spec/frontend/code_navigation/store/mutations_spec.js b/spec/frontend/code_navigation/store/mutations_spec.js
index d4a75da429e..cb10729f4b6 100644
--- a/spec/frontend/code_navigation/store/mutations_spec.js
+++ b/spec/frontend/code_navigation/store/mutations_spec.js
@@ -12,11 +12,11 @@ describe('Code navigation mutations', () => {
it('sets initial data', () => {
mutations.SET_INITIAL_DATA(state, {
blobs: ['test'],
- definitionPathPrefix: 'https://test.com/blob/master',
+ definitionPathPrefix: 'https://test.com/blob/main',
});
expect(state.blobs).toEqual(['test']);
- expect(state.definitionPathPrefix).toBe('https://test.com/blob/master');
+ expect(state.definitionPathPrefix).toBe('https://test.com/blob/main');
});
});
diff --git a/spec/frontend/commits_spec.js b/spec/frontend/commits_spec.js
index 954025091cf..8189ebe6e55 100644
--- a/spec/frontend/commits_spec.js
+++ b/spec/frontend/commits_spec.js
@@ -10,7 +10,7 @@ describe('Commits List', () => {
beforeEach(() => {
setFixtures(`
- <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master">
+ <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/main">
<input id="commits-search">
</form>
<ol id="commits-list"></ol>
@@ -59,7 +59,7 @@ describe('Commits List', () => {
jest.spyOn(window.history, 'replaceState').mockImplementation(() => {});
mock = new MockAdapter(axios);
- mock.onGet('/h5bp/html5-boilerplate/commits/master').reply(200, {
+ mock.onGet('/h5bp/html5-boilerplate/commits/main').reply(200, {
html: '<li>Result</li>',
});
diff --git a/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
index 15b052fffbb..3f812d3cf4e 100644
--- a/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
+++ b/spec/frontend/contributors/component/__snapshots__/contributors_spec.js.snap
@@ -8,7 +8,7 @@ exports[`Contributors charts should render charts when loading completed and the
<h4
class="gl-mb-2 gl-mt-5"
>
- Commits to master
+ Commits to main
</h4>
<span>
diff --git a/spec/frontend/contributors/component/contributors_spec.js b/spec/frontend/contributors/component/contributors_spec.js
index de55be4aa72..cb7e13b9fed 100644
--- a/spec/frontend/contributors/component/contributors_spec.js
+++ b/spec/frontend/contributors/component/contributors_spec.js
@@ -10,7 +10,7 @@ let mock;
let store;
const Component = Vue.extend(ContributorsCharts);
const endpoint = 'contributors';
-const branch = 'master';
+const branch = 'main';
const chartData = [
{ author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-05-05' },
{ author_name: 'John', author_email: 'jawnnypoo@gmail.com', date: '2019-03-03' },
diff --git a/spec/frontend/diffs/mock_data/diff_metadata.js b/spec/frontend/diffs/mock_data/diff_metadata.js
index cfa0038c06f..ce79843b8b1 100644
--- a/spec/frontend/diffs/mock_data/diff_metadata.js
+++ b/spec/frontend/diffs/mock_data/diff_metadata.js
@@ -3,7 +3,7 @@ export const diffMetadata = {
size: 1,
branch_name: 'update-changelog',
source_branch_exists: true,
- target_branch_name: 'master',
+ target_branch_name: 'main',
commit: null,
context_commits: null,
merge_request_diff: {
diff --git a/spec/frontend/notes/old_notes_spec.js b/spec/frontend/notes/old_notes_spec.js
index 432b660c4b3..0cf43b8fd97 100644
--- a/spec/frontend/notes/old_notes_spec.js
+++ b/spec/frontend/notes/old_notes_spec.js
@@ -28,7 +28,7 @@ window.gl = window.gl || {};
gl.utils = gl.utils || {};
gl.utils.disableButtonIfEmptyField = () => {};
-// the following test is unreliable and failing in master 2-3 times a day
+// the following test is unreliable and failing in main 2-3 times a day
// see https://gitlab.com/gitlab-org/gitlab/issues/206906#note_290602581
// eslint-disable-next-line jest/no-disabled-tests
describe.skip('Old Notes (~/notes.js)', () => {
diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js
index 11d4fe0e206..de1d5c557ce 100644
--- a/spec/frontend/ref/stores/mutations_spec.js
+++ b/spec/frontend/ref/stores/mutations_spec.js
@@ -108,7 +108,7 @@ describe('Ref selector Vuex store mutations', () => {
const response = {
data: [
{
- name: 'master',
+ name: 'main',
default: true,
// everything except "name" and "default" should be stripped
@@ -130,7 +130,7 @@ describe('Ref selector Vuex store mutations', () => {
expect(state.matches.branches).toEqual({
list: [
{
- name: 'master',
+ name: 'main',
default: true,
},
{
diff --git a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
index 55bb7dbe5c0..d29048d640c 100644
--- a/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
+++ b/spec/frontend/reports/grouped_test_report/grouped_test_reports_app_spec.js
@@ -279,9 +279,7 @@ describe('Grouped test reports app', () => {
});
it('renders the recent failures count on the test case', () => {
- expect(findIssueRecentFailures().text()).toBe(
- 'Failed 8 times in master in the last 14 days',
- );
+ expect(findIssueRecentFailures().text()).toBe('Failed 8 times in main in the last 14 days');
});
});
diff --git a/spec/frontend/reports/grouped_test_report/store/mutations_spec.js b/spec/frontend/reports/grouped_test_report/store/mutations_spec.js
index d8642a9b440..b2890d7285f 100644
--- a/spec/frontend/reports/grouped_test_report/store/mutations_spec.js
+++ b/spec/frontend/reports/grouped_test_report/store/mutations_spec.js
@@ -52,7 +52,7 @@ describe('Reports Store Mutations', () => {
system_output: "Failure/Error: is_expected.to eq('gitlab')",
recent_failures: {
count: 4,
- base_branch: 'master',
+ base_branch: 'main',
},
},
],
diff --git a/spec/frontend/reports/mock_data/mock_data.js b/spec/frontend/reports/mock_data/mock_data.js
index 68c7439df47..2599b0ac365 100644
--- a/spec/frontend/reports/mock_data/mock_data.js
+++ b/spec/frontend/reports/mock_data/mock_data.js
@@ -7,7 +7,7 @@ export const failedIssue = {
"Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in \u003ctop (required)\u003e'",
recent_failures: {
count: 3,
- base_branch: 'master',
+ base_branch: 'main',
},
};
diff --git a/spec/frontend/reports/mock_data/recent_failures_report.json b/spec/frontend/reports/mock_data/recent_failures_report.json
index bc86d788ee2..c4a5fb78dcd 100644
--- a/spec/frontend/reports/mock_data/recent_failures_report.json
+++ b/spec/frontend/reports/mock_data/recent_failures_report.json
@@ -12,7 +12,7 @@
"system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'",
"recent_failures": {
"count": 8,
- "base_branch": "master"
+ "base_branch": "main"
}
},
{
@@ -38,7 +38,7 @@
"execution_time": 0.000562,
"recent_failures": {
"count": 3,
- "base_branch": "master"
+ "base_branch": "main"
}
}
],
diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb
index 5bbe32166ab..780bb0a1408 100644
--- a/spec/helpers/learn_gitlab_helper_spec.rb
+++ b/spec/helpers/learn_gitlab_helper_spec.rb
@@ -85,10 +85,14 @@ RSpec.describe LearnGitlabHelper do
it { is_expected.to eq(result) }
end
+ end
- context 'when not signed in' do
- it { is_expected.to eq(false) }
+ context 'when not signed in' do
+ before do
+ stub_experiment_for_subject(learn_gitlab_a: true, learn_gitlab_b: true)
end
+
+ it { is_expected.to eq(false) }
end
end
@@ -118,10 +122,14 @@ RSpec.describe LearnGitlabHelper do
it { is_expected.to eq(result) }
end
+ end
- context 'when not signed in' do
- it { is_expected.to eq(nil) }
+ context 'when not signed in' do
+ before do
+ stub_experiment_for_subject(learn_gitlab_a: true, learn_gitlab_b: true)
end
+
+ it { is_expected.to eq(nil) }
end
end
end
diff --git a/spec/lib/gitlab/background_migration/update_timelogs_project_id_spec.rb b/spec/lib/gitlab/background_migration/update_timelogs_project_id_spec.rb
new file mode 100644
index 00000000000..fc4d776b8be
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/update_timelogs_project_id_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::UpdateTimelogsProjectId, schema: 20210427212034 do
+ let!(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') }
+ let!(:project1) { table(:projects).create!(namespace_id: namespace.id) }
+ let!(:project2) { table(:projects).create!(namespace_id: namespace.id) }
+ let!(:issue1) { table(:issues).create!(project_id: project1.id) }
+ let!(:issue2) { table(:issues).create!(project_id: project2.id) }
+ let!(:merge_request1) { table(:merge_requests).create!(target_project_id: project1.id, source_branch: 'master', target_branch: 'feature') }
+ let!(:merge_request2) { table(:merge_requests).create!(target_project_id: project2.id, source_branch: 'master', target_branch: 'feature') }
+ let!(:timelog1) { table(:timelogs).create!(issue_id: issue1.id, time_spent: 60) }
+ let!(:timelog2) { table(:timelogs).create!(issue_id: issue1.id, time_spent: 60) }
+ let!(:timelog3) { table(:timelogs).create!(issue_id: issue2.id, time_spent: 60) }
+ let!(:timelog4) { table(:timelogs).create!(merge_request_id: merge_request1.id, time_spent: 600) }
+ let!(:timelog5) { table(:timelogs).create!(merge_request_id: merge_request1.id, time_spent: 600) }
+ let!(:timelog6) { table(:timelogs).create!(merge_request_id: merge_request2.id, time_spent: 600) }
+ let!(:timelog7) { table(:timelogs).create!(issue_id: issue2.id, time_spent: 60, project_id: project1.id) }
+ let!(:timelog8) { table(:timelogs).create!(merge_request_id: merge_request2.id, time_spent: 600, project_id: project1.id) }
+
+ describe '#perform' do
+ context 'when timelogs belong to issues' do
+ it 'sets correct project_id' do
+ subject.perform(timelog1.id, timelog3.id)
+
+ expect(timelog1.reload.project_id).to eq(issue1.project_id)
+ expect(timelog2.reload.project_id).to eq(issue1.project_id)
+ expect(timelog3.reload.project_id).to eq(issue2.project_id)
+ end
+ end
+
+ context 'when timelogs belong to merge requests' do
+ it 'sets correct project ids' do
+ subject.perform(timelog4.id, timelog6.id)
+
+ expect(timelog4.reload.project_id).to eq(merge_request1.target_project_id)
+ expect(timelog5.reload.project_id).to eq(merge_request1.target_project_id)
+ expect(timelog6.reload.project_id).to eq(merge_request2.target_project_id)
+ end
+ end
+
+ context 'when timelogs already belong to projects' do
+ it 'does not update the project id' do
+ subject.perform(timelog7.id, timelog8.id)
+
+ expect(timelog7.reload.project_id).to eq(project1.id)
+ expect(timelog8.reload.project_id).to eq(project1.id)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/schedule_update_timelogs_project_id_spec.rb b/spec/migrations/schedule_update_timelogs_project_id_spec.rb
new file mode 100644
index 00000000000..e2972d2fd08
--- /dev/null
+++ b/spec/migrations/schedule_update_timelogs_project_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20210427212034_schedule_update_timelogs_project_id.rb')
+
+RSpec.describe ScheduleUpdateTimelogsProjectId do
+ let!(:namespace) { table(:namespaces).create!(name: 'namespace', path: 'namespace') }
+ let!(:project) { table(:projects).create!(namespace_id: namespace.id) }
+ let!(:issue) { table(:issues).create!(project_id: project.id) }
+ let!(:merge_request) { table(:merge_requests).create!(target_project_id: project.id, source_branch: 'master', target_branch: 'feature') }
+ let!(:timelog1) { table(:timelogs).create!(issue_id: issue.id, time_spent: 60) }
+ let!(:timelog2) { table(:timelogs).create!(merge_request_id: merge_request.id, time_spent: 600) }
+ let!(:timelog3) { table(:timelogs).create!(merge_request_id: merge_request.id, time_spent: 60) }
+ let!(:timelog4) { table(:timelogs).create!(issue_id: issue.id, time_spent: 600) }
+
+ it 'correctly schedules background migrations' do
+ stub_const("#{described_class}::BATCH_SIZE", 2)
+
+ Sidekiq::Testing.fake! do
+ freeze_time do
+ migrate!
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(2.minutes, timelog1.id, timelog2.id)
+
+ expect(described_class::MIGRATION)
+ .to be_scheduled_delayed_migration(4.minutes, timelog3.id, timelog4.id)
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/migrations/update_invalid_web_hooks_spec.rb b/spec/migrations/update_invalid_web_hooks_spec.rb
new file mode 100644
index 00000000000..a65f82d7082
--- /dev/null
+++ b/spec/migrations/update_invalid_web_hooks_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe UpdateInvalidWebHooks do
+ let(:web_hooks) { table(:web_hooks) }
+ let(:groups) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+
+ before do
+ group = groups.create!(name: 'gitlab', path: 'gitlab-org')
+ project = projects.create!(namespace_id: group.id)
+
+ web_hooks.create!(group_id: group.id, type: 'GroupHook')
+ web_hooks.create!(project_id: project.id, type: 'ProjectHook')
+ web_hooks.create!(group_id: group.id, project_id: project.id, type: 'ProjectHook')
+ end
+
+ it 'clears group_id when ProjectHook type and project_id are present', :aggregate_failures do
+ expect(web_hooks.where.not(group_id: nil).where.not(project_id: nil).count).to eq(1)
+
+ migrate!
+
+ expect(web_hooks.where.not(group_id: nil).where.not(project_id: nil).count).to eq(0)
+ expect(web_hooks.where(type: 'GroupHook').count).to eq(1)
+ expect(web_hooks.where(type: 'ProjectHook').count).to eq(2)
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 07031f16410..0c35b1265ba 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -83,6 +83,7 @@ RSpec.describe User do
it { is_expected.to have_one(:user_detail) }
it { is_expected.to have_one(:atlassian_identity) }
it { is_expected.to have_one(:user_highest_role) }
+ it { is_expected.to have_one(:credit_card_validation) }
it { is_expected.to have_many(:snippets).dependent(:destroy) }
it { is_expected.to have_many(:members) }
it { is_expected.to have_many(:project_members) }
@@ -1387,6 +1388,26 @@ RSpec.describe User do
end
end
+ describe '#credit_card_validated_at' do
+ let_it_be(:user) { create(:user) }
+
+ context 'when credit_card_validation does not exist' do
+ it 'returns nil' do
+ expect(user.credit_card_validated_at).to be nil
+ end
+ end
+
+ context 'when credit_card_validation exists' do
+ it 'returns the credit card validated time' do
+ credit_card_validated_time = Time.current - 1.day
+
+ create(:credit_card_validation, credit_card_validated_at: credit_card_validated_time, user: user)
+
+ expect(user.credit_card_validated_at).to eq(credit_card_validated_time)
+ end
+ end
+ end
+
describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do
let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
let(:user) { create(:user) }
@@ -5290,6 +5311,26 @@ RSpec.describe User do
end
end
+ describe 'user credit card validation' do
+ context 'when user is initialized' do
+ let(:user) { build(:user) }
+
+ it { expect(user.credit_card_validation).not_to be_present }
+ end
+
+ context 'when create user without credit card validation' do
+ let(:user) { create(:user) }
+
+ it { expect(user.credit_card_validation).not_to be_present }
+ end
+
+ context 'when user credit card validation exists' do
+ let(:user) { create(:user, :with_credit_card_validation) }
+
+ it { expect(user.credit_card_validation).to be_persisted }
+ end
+ end
+
describe 'user detail' do
context 'when user is initialized' do
let(:user) { build(:user) }
diff --git a/spec/models/users/credit_card_validation_spec.rb b/spec/models/users/credit_card_validation_spec.rb
new file mode 100644
index 00000000000..fb9f6e35038
--- /dev/null
+++ b/spec/models/users/credit_card_validation_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Users::CreditCardValidation do
+ it { is_expected.to belong_to(:user) }
+end