summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:11:55 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-25 18:11:55 +0000
commit2b2d833ab3e78f8c9f626af950a16d43fc38c9f8 (patch)
tree13c101679f3cda5808affea46709207a24f4a3c9 /spec
parent7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (diff)
downloadgitlab-ce-2b2d833ab3e78f8c9f626af950a16d43fc38c9f8.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/container_repositories.rb46
-rw-r--r--spec/frontend/batch_comments/components/draft_note_spec.js33
-rw-r--r--spec/graphql/resolvers/ci/runners_resolver_spec.rb111
-rw-r--r--spec/lib/container_registry/client_spec.rb110
-rw-r--r--spec/lib/container_registry/gitlab_api_client_spec.rb178
-rw-r--r--spec/lib/container_registry/migration_spec.rb142
-rw-r--r--spec/lib/container_registry/registry_spec.rb6
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/saver_spec.rb44
-rw-r--r--spec/mailers/notify_spec.rb2
-rw-r--r--spec/models/ability_spec.rb2
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb2
-rw-r--r--spec/models/ci/job_token/project_scope_link_spec.rb7
-rw-r--r--spec/models/ci/pipeline_spec.rb4
-rw-r--r--spec/models/ci/trigger_spec.rb2
-rw-r--r--spec/models/commit_spec.rb2
-rw-r--r--spec/models/container_repository_spec.rb333
-rw-r--r--spec/models/environment_status_spec.rb2
-rw-r--r--spec/models/event_spec.rb20
-rw-r--r--spec/models/external_pull_request_spec.rb7
-rw-r--r--spec/models/group_spec.rb2
-rw-r--r--spec/models/issue_collection_spec.rb4
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/note_spec.rb2
-rw-r--r--spec/models/project_spec.rb4
-rw-r--r--spec/models/project_team_spec.rb8
-rw-r--r--spec/policies/ci/pipeline_policy_spec.rb6
-rw-r--r--spec/policies/namespaces/project_namespace_policy_spec.rb2
-rw-r--r--spec/policies/project_member_policy_spec.rb2
-rw-r--r--spec/policies/project_policy_spec.rb4
-rw-r--r--spec/rubocop/cop/file_decompression_spec.rb48
-rw-r--r--spec/services/service_ping/submit_service_ping_service_spec.rb15
-rw-r--r--spec/services/update_container_registry_info_service_spec.rb15
-rw-r--r--spec/spec_helper.rb10
-rw-r--r--spec/support/flaky_tests.rb11
-rw-r--r--spec/support/helpers/features/iteration_helpers.rb6
-rw-r--r--spec/support/shared_contexts/lib/container_registry/client_shared_context.rb27
-rw-r--r--spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb2
-rw-r--r--spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb6
-rw-r--r--spec/tooling/rspec_flaky/config_spec.rb33
-rw-r--r--spec/tooling/rspec_flaky/listener_spec.rb6
41 files changed, 1106 insertions, 165 deletions
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 86bb129067f..ce83e9e8006 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -33,6 +33,52 @@ FactoryBot.define do
expiration_policy_cleanup_status { :cleanup_ongoing }
end
+ trait :default do
+ migration_state { 'default' }
+ end
+
+ trait :pre_importing do
+ migration_state { 'pre_importing' }
+ migration_pre_import_started_at { Time.zone.now }
+ end
+
+ trait :pre_import_done do
+ migration_state { 'pre_import_done' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ end
+
+ trait :importing do
+ migration_state { 'importing' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ end
+
+ trait :import_done do
+ migration_state { 'import_done' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ migration_import_done_at { Time.zone.now }
+ end
+
+ trait :import_aborted do
+ migration_state { 'import_aborted' }
+ migration_pre_import_started_at { Time.zone.now }
+ migration_pre_import_done_at { Time.zone.now }
+ migration_import_started_at { Time.zone.now }
+ migration_aborted_at { Time.zone.now }
+ migration_aborted_in_state { 'importing' }
+ migration_retries_count { 1 }
+ end
+
+ trait :import_skipped do
+ migration_state { 'import_skipped' }
+ migration_skipped_at { Time.zone.now }
+ migration_skipped_reason { :too_many_tags }
+ end
+
after(:build) do |repository, evaluator|
next if evaluator.tags.to_a.none?
diff --git a/spec/frontend/batch_comments/components/draft_note_spec.js b/spec/frontend/batch_comments/components/draft_note_spec.js
index f93daf91c2f..145af3a2d5b 100644
--- a/spec/frontend/batch_comments/components/draft_note_spec.js
+++ b/spec/frontend/batch_comments/components/draft_note_spec.js
@@ -1,8 +1,10 @@
+import { nextTick } from 'vue';
+import { GlButton } from '@gitlab/ui';
import { getByRole } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
import { stubComponent } from 'helpers/stub_component';
import DraftNote from '~/batch_comments/components/draft_note.vue';
+import PublishButton from '~/batch_comments/components/publish_button.vue';
import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue';
import '~/behaviors/markdown/render_gfm';
@@ -28,6 +30,8 @@ describe('Batch comments draft note component', () => {
};
const getList = () => getByRole(wrapper.element, 'list');
+ const findSubmitReviewButton = () => wrapper.findComponent(PublishButton);
+ const findAddCommentButton = () => wrapper.findComponent(GlButton);
const createComponent = (propsData = { draft }) => {
wrapper = shallowMount(DraftNote, {
@@ -63,7 +67,7 @@ describe('Batch comments draft note component', () => {
describe('add comment now', () => {
it('dispatches publishSingleDraft when clicking', () => {
createComponent();
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
+ const publishNowButton = findAddCommentButton();
publishNowButton.vm.$emit('click');
expect(wrapper.vm.$store.dispatch).toHaveBeenCalledWith(
@@ -77,10 +81,33 @@ describe('Batch comments draft note component', () => {
wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
await nextTick();
- const publishNowButton = wrapper.find({ ref: 'publishNowButton' });
+ const publishNowButton = findAddCommentButton();
expect(publishNowButton.props().loading).toBe(true);
});
+
+ it('sets as disabled when review is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.isPublishing = true;
+
+ await nextTick();
+ const publishNowButton = findAddCommentButton();
+
+ expect(publishNowButton.props().disabled).toBe(true);
+ expect(publishNowButton.props().loading).toBe(false);
+ });
+ });
+
+ describe('submit review', () => {
+ it('sets as disabled when draft is publishing', async () => {
+ createComponent();
+ wrapper.vm.$store.state.batchComments.currentlyPublishingDrafts.push(1);
+
+ await nextTick();
+ const publishNowButton = findSubmitReviewButton();
+
+ expect(publishNowButton.attributes().disabled).toBeTruthy();
+ });
});
describe('update', () => {
diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
index df6490df915..9251fbf24d9 100644
--- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb
+++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb
@@ -43,34 +43,99 @@ RSpec.describe Resolvers::Ci::RunnersResolver do
# Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back.
describe 'Allowed query arguments' do
let(:finder) { instance_double(::Ci::RunnersFinder) }
- let(:args) do
- {
- active: true,
- status: 'active',
- type: :instance_type,
- tag_list: ['active_runner'],
- search: 'abc',
- sort: :contacted_asc
- }
+
+ context 'with active filter' do
+ let(:args) do
+ {
+ active: true,
+ status: 'active',
+ type: :instance_type,
+ tag_list: ['active_runner'],
+ search: 'abc',
+ sort: :contacted_asc
+ }
+ end
+
+ let(:expected_params) do
+ {
+ active: true,
+ status_status: 'active',
+ type_type: :instance_type,
+ tag_name: ['active_runner'],
+ preload: { tag_name: nil },
+ search: 'abc',
+ sort: 'contacted_asc'
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
+ end
+
+ context 'with both active and paused filter' do
+ let(:args) do
+ {
+ active: true,
+ paused: true
+ }
+ end
+
+ let(:expected_params) do
+ {
+ active: false,
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
- let(:expected_params) do
- {
- active: true,
- status_status: 'active',
- type_type: :instance_type,
- tag_name: ['active_runner'],
- preload: { tag_name: nil },
- search: 'abc',
- sort: 'contacted_asc'
- }
+ context 'with paused filter' do
+ let(:args) do
+ { paused: true }
+ end
+
+ let(:expected_params) do
+ {
+ active: false,
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
- it 'calls RunnersFinder with expected arguments' do
- allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
- allow(finder).to receive(:execute).once.and_return([:execute_return_value])
+ context 'with neither paused or active filters' do
+ let(:args) do
+ {}
+ end
+
+ let(:expected_params) do
+ {
+ preload: { tag_name: nil }
+ }
+ end
+
+ it 'calls RunnersFinder with expected arguments' do
+ expect(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder)
+ allow(finder).to receive(:execute).once.and_return([:execute_return_value])
- expect(subject.items.to_a).to eq([:execute_return_value])
+ expect(subject.items.to_a).to eq([:execute_return_value])
+ end
end
end
end
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 259d7d5ad13..974c3478ddc 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -5,29 +5,7 @@ require 'spec_helper'
RSpec.describe ContainerRegistry::Client do
using RSpec::Parameterized::TableSyntax
- let(:token) { '12345' }
- let(:options) { { token: token } }
- let(:registry_api_url) { 'http://container-registry' }
- let(:client) { described_class.new(registry_api_url, options) }
- let(:push_blob_headers) do
- {
- 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
- 'Authorization' => "bearer #{token}",
- 'Content-Type' => 'application/octet-stream',
- 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
- }
- end
-
- let(:headers_with_accept_types) do
- {
- 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
- 'Authorization' => "bearer #{token}",
- 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
- }
- end
-
- let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } }
- let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS }
+ include_context 'container registry client'
shared_examples 'handling timeouts' do
let(:retry_options) do
@@ -48,14 +26,14 @@ RSpec.describe ContainerRegistry::Client do
retry_block: -> (_, _, _, _) { actual_retries += 1 }
)
- stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options_with_block)
+ stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options_with_block)
expect { subject }.to raise_error(Faraday::ConnectionFailed)
expect(actual_retries).to eq(retry_options_with_block[:max])
end
it 'logs the error' do
- stub_const('ContainerRegistry::Client::RETRY_OPTIONS', retry_options)
+ stub_const('ContainerRegistry::BaseClient::RETRY_OPTIONS', retry_options)
expect(Gitlab::ErrorTracking)
.to receive(:log_exception)
@@ -63,7 +41,7 @@ RSpec.describe ContainerRegistry::Client do
.times
.with(
an_instance_of(Faraday::ConnectionFailed),
- class: described_class.name,
+ class: ::ContainerRegistry::BaseClient.name,
url: URI(url)
)
@@ -325,14 +303,14 @@ RSpec.describe ContainerRegistry::Client do
subject { client.supports_tag_delete? }
where(:registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
- true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true
- true | true | [] | true | true
- true | false | [] | true | true
- false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false
- false | true | [] | true | false
- false | false | [] | true | false
+ true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false
+ false | true | [] | true | false
+ false | false | [] | true | false
end
with_them do
@@ -366,38 +344,38 @@ RSpec.describe ContainerRegistry::Client do
subject { described_class.supports_tag_delete? }
where(:registry_api_url, :registry_enabled, :registry_tags_support_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
- 'http://sandbox.local' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- 'http://sandbox.local' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | true
- 'http://sandbox.local' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | true
- 'http://sandbox.local' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | true | false
- 'http://sandbox.local' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- 'http://sandbox.local' | true | true | true | [] | true | true
- 'http://sandbox.local' | true | true | false | [] | true | true
- 'http://sandbox.local' | true | false | true | [] | true | false
- 'http://sandbox.local' | true | false | false | [] | true | false
- 'http://sandbox.local' | false | true | true | [] | false | false
- 'http://sandbox.local' | false | true | false | [] | false | false
- 'http://sandbox.local' | false | false | true | [] | false | false
- 'http://sandbox.local' | false | false | false | [] | false | false
- '' | true | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | true | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | true | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | false | true | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | false | false | false | [ContainerRegistry::Client::REGISTRY_TAG_DELETE_FEATURE] | false | false
- '' | true | true | true | [] | false | false
- '' | true | true | false | [] | false | false
- '' | true | false | true | [] | false | false
- '' | true | false | false | [] | false | false
- '' | false | true | true | [] | false | false
- '' | false | true | false | [] | false | false
- '' | false | false | true | [] | false | false
- '' | false | false | false | [] | false | false
+ 'http://sandbox.local' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ 'http://sandbox.local' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | true
+ 'http://sandbox.local' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | true
+ 'http://sandbox.local' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | true | false
+ 'http://sandbox.local' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ 'http://sandbox.local' | true | true | true | [] | true | true
+ 'http://sandbox.local' | true | true | false | [] | true | true
+ 'http://sandbox.local' | true | false | true | [] | true | false
+ 'http://sandbox.local' | true | false | false | [] | true | false
+ 'http://sandbox.local' | false | true | true | [] | false | false
+ 'http://sandbox.local' | false | true | false | [] | false | false
+ 'http://sandbox.local' | false | false | true | [] | false | false
+ 'http://sandbox.local' | false | false | false | [] | false | false
+ '' | true | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | true | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | true | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | false | true | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | false | false | false | [described_class::REGISTRY_TAG_DELETE_FEATURE] | false | false
+ '' | true | true | true | [] | false | false
+ '' | true | true | false | [] | false | false
+ '' | true | false | true | [] | false | false
+ '' | true | false | false | [] | false | false
+ '' | false | true | true | [] | false | false
+ '' | false | true | false | [] | false | false
+ '' | false | false | true | [] | false | false
+ '' | false | false | false | [] | false | false
end
with_them do
diff --git a/spec/lib/container_registry/gitlab_api_client_spec.rb b/spec/lib/container_registry/gitlab_api_client_spec.rb
new file mode 100644
index 00000000000..251e15390b1
--- /dev/null
+++ b/spec/lib/container_registry/gitlab_api_client_spec.rb
@@ -0,0 +1,178 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::GitlabApiClient do
+ using RSpec::Parameterized::TableSyntax
+
+ include_context 'container registry client'
+
+ describe '#supports_gitlab_api?' do
+ subject { client.supports_gitlab_api? }
+
+ where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false
+ false | true | [] | true | false
+ false | false | [] | true | false
+ end
+
+ with_them do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com)
+ stub_registry_gitlab_api_support(registry_gitlab_api_enabled)
+ stub_application_setting(container_registry_features: container_registry_features)
+ end
+
+ it 'returns the expected result' do
+ if expect_registry_to_be_pinged
+ expect_next_instance_of(Faraday::Connection) do |connection|
+ expect(connection).to receive(:run_request).and_call_original
+ end
+ else
+ expect(Faraday::Connection).not_to receive(:new)
+ end
+
+ expect(subject).to be expected_result
+ end
+ end
+
+ context 'with 401 response' do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(false)
+ stub_application_setting(container_registry_features: [])
+ stub_request(:get, "#{registry_api_url}/gitlab/v1/")
+ .to_return(status: 401, body: '')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#pre_import_repository' do
+ let(:path) { 'namespace/path/to/repository' }
+
+ subject { client.pre_import_repository('namespace/path/to/repository') }
+
+ where(:status_code, :expected_result) do
+ 200 | :already_imported
+ 202 | :ok
+ 401 | :unauthorized
+ 404 | :not_found
+ 409 | :already_being_imported
+ 418 | :error
+ 424 | :pre_import_failed
+ 425 | :already_being_imported
+ 429 | :too_many_imports
+ end
+
+ with_them do
+ before do
+ stub_pre_import(path, status_code, pre: true)
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '#pre_import_repository' do
+ let(:path) { 'namespace/path/to/repository' }
+
+ subject { client.import_repository('namespace/path/to/repository') }
+
+ where(:status_code, :expected_result) do
+ 200 | :already_imported
+ 202 | :ok
+ 401 | :unauthorized
+ 404 | :not_found
+ 409 | :already_being_imported
+ 418 | :error
+ 424 | :pre_import_failed
+ 425 | :already_being_imported
+ 429 | :too_many_imports
+ end
+
+ with_them do
+ before do
+ stub_pre_import(path, status_code, pre: false)
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.supports_gitlab_api?' do
+ subject { described_class.supports_gitlab_api? }
+
+ where(:registry_gitlab_api_enabled, :is_on_dot_com, :container_registry_features, :expect_registry_to_be_pinged, :expected_result) do
+ true | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ true | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | true
+ false | true | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | false | true
+ false | false | [described_class::REGISTRY_GITLAB_V1_API_FEATURE] | true | false
+ true | true | [] | true | true
+ true | false | [] | true | true
+ false | true | [] | true | false
+ false | false | [] | true | false
+ end
+
+ with_them do
+ before do
+ allow(::Gitlab).to receive(:com?).and_return(is_on_dot_com)
+ stub_container_registry_config(enabled: true, api_url: registry_api_url, key: 'spec/fixtures/x509_certificate_pk.key')
+ stub_registry_gitlab_api_support(registry_gitlab_api_enabled)
+ stub_application_setting(container_registry_features: container_registry_features)
+ end
+
+ it 'returns the expected result' do
+ if expect_registry_to_be_pinged
+ expect_next_instance_of(Faraday::Connection) do |connection|
+ expect(connection).to receive(:run_request).and_call_original
+ end
+ else
+ expect(Faraday::Connection).not_to receive(:new)
+ end
+
+ expect(subject).to be expected_result
+ end
+ end
+
+ context 'with the registry disabled' do
+ before do
+ stub_container_registry_config(enabled: false, api_url: 'http://sandbox.local', key: 'spec/fixtures/x509_certificate_pk.key')
+ end
+
+ it 'returns false' do
+ expect(Faraday::Connection).not_to receive(:new)
+
+ expect(subject).to be_falsey
+ end
+ end
+
+ context 'with a blank registry url' do
+ before do
+ stub_container_registry_config(enabled: true, api_url: '', key: 'spec/fixtures/x509_certificate_pk.key')
+ end
+
+ it 'returns false' do
+ expect(Faraday::Connection).not_to receive(:new)
+
+ expect(subject).to be_falsey
+ end
+ end
+ end
+
+ def stub_pre_import(path, status_code, pre:)
+ stub_request(:put, "#{registry_api_url}/gitlab/v1/import/#{path}?pre=#{pre}")
+ .to_return(status: status_code, body: '')
+ end
+
+ def stub_registry_gitlab_api_support(supported = true)
+ status_code = supported ? 200 : 404
+ stub_request(:get, "#{registry_api_url}/gitlab/v1/")
+ .to_return(status: status_code, body: '')
+ end
+end
diff --git a/spec/lib/container_registry/migration_spec.rb b/spec/lib/container_registry/migration_spec.rb
new file mode 100644
index 00000000000..cc20fc24d33
--- /dev/null
+++ b/spec/lib/container_registry/migration_spec.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ContainerRegistry::Migration do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '.enabled?' do
+ subject { described_class.enabled? }
+
+ it { is_expected.to eq(true) }
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(container_registry_migration_phase2_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ describe '.enqueue_waiting_time' do
+ subject { described_class.enqueue_waiting_time }
+
+ where(:slow_enabled, :fast_enabled, :expected_result) do
+ false | false | 1.hour
+ true | false | 6.hours
+ false | true | 0
+ true | true | 0
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(
+ container_registry_migration_phase2_enqueue_speed_slow: slow_enabled,
+ container_registry_migration_phase2_enqueue_speed_fast: fast_enabled
+ )
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.capacity' do
+ subject { described_class.capacity }
+
+ where(:ff_1_enabled, :ff_10_enabled, :ff_25_enabled, :expected_result) do
+ false | false | false | 0
+ true | false | false | 1
+ true | true | false | 10
+ true | true | true | 25
+ false | true | false | 10
+ false | true | true | 25
+ false | false | true | 25
+ true | false | true | 25
+ end
+
+ with_them do
+ before do
+ stub_feature_flags(
+ container_registry_migration_phase2_capacity_1: ff_1_enabled,
+ container_registry_migration_phase2_capacity_10: ff_10_enabled,
+ container_registry_migration_phase2_capacity_25: ff_25_enabled
+ )
+ end
+
+ it { is_expected.to eq(expected_result) }
+ end
+ end
+
+ describe '.max_tags_count' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_max_tags_count: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_tags_count).to eq(value)
+ end
+ end
+
+ describe '.max_retries' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_max_retries: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_retries).to eq(value)
+ end
+ end
+
+ describe '.start_max_retries' do
+ let(:value) { 1 }
+
+ before do
+ stub_application_setting(container_registry_import_start_max_retries: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.start_max_retries).to eq(value)
+ end
+ end
+
+ describe '.max_step_duration' do
+ let(:value) { 5.minutes }
+
+ before do
+ stub_application_setting(container_registry_import_max_step_duration: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.max_step_duration).to eq(value)
+ end
+ end
+
+ describe '.target_plan_name' do
+ let(:value) { 'free' }
+
+ before do
+ stub_application_setting(container_registry_import_target_plan: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.target_plan_name).to eq(value)
+ end
+ end
+
+ describe '.created_before' do
+ let(:value) { 1.day.ago }
+
+ before do
+ stub_application_setting(container_registry_import_created_before: value)
+ end
+
+ it 'returns the matching application_setting' do
+ expect(described_class.created_before).to eq(value)
+ end
+ end
+end
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index d6e2b17f53b..405ef1170ae 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -27,4 +27,10 @@ RSpec.describe ContainerRegistry::Registry do
it { is_expected.to eq(path) }
end
end
+
+ describe '#gitlab_api_client' do
+ subject { registry.gitlab_api_client }
+
+ it { is_expected.to be_instance_of(ContainerRegistry::GitlabApiClient) }
+ end
end
diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
index 112ff2c7380..3a8c14fc2f6 100644
--- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
+++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe 'cross-database foreign keys' do
ci_freeze_periods.project_id
ci_job_artifacts.project_id
ci_job_token_project_scope_links.added_by_id
- ci_job_token_project_scope_links.source_project_id
ci_job_token_project_scope_links.target_project_id
ci_pending_builds.namespace_id
ci_pending_builds.project_id
@@ -33,11 +32,9 @@ RSpec.describe 'cross-database foreign keys' do
ci_running_builds.project_id
ci_sources_projects.source_project_id
ci_stages.project_id
- ci_subscriptions_projects.downstream_project_id
ci_subscriptions_projects.upstream_project_id
ci_unit_tests.project_id
dast_site_profiles_pipelines.ci_pipeline_id
- external_pull_requests.project_id
vulnerability_feedback.pipeline_id
).freeze
end
diff --git a/spec/lib/gitlab/import_export/saver_spec.rb b/spec/lib/gitlab/import_export/saver_spec.rb
index 877474dd862..90accfaf3a9 100644
--- a/spec/lib/gitlab/import_export/saver_spec.rb
+++ b/spec/lib/gitlab/import_export/saver_spec.rb
@@ -36,6 +36,32 @@ RSpec.describe Gitlab::ImportExport::Saver do
.to match(%r[\/uploads\/-\/system\/import_export_upload\/export_file.*])
end
+ it 'logs metrics after saving' do
+ stub_uploads_object_storage(ImportExportUploader)
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive saved',
+ exportable_class: 'Project',
+ 'correlation_id' => anything,
+ archive_file: anything,
+ compress_duration_s: anything
+ )).and_call_original
+
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive uploaded',
+ exportable_class: 'Project',
+ 'correlation_id' => anything,
+ archive_file: anything,
+ compress_duration_s: anything,
+ assign_duration_s: anything,
+ upload_duration_s: anything,
+ upload_bytes: anything
+ )).and_call_original
+
+ subject.save
+ end
+
it 'removes archive path and keeps base path untouched' do
allow(shared).to receive(:archive_path).and_return(archive_path)
@@ -45,4 +71,22 @@ RSpec.describe Gitlab::ImportExport::Saver do
expect(FileUtils).to have_received(:rm_rf).with(archive_path)
expect(Dir.exist?(archive_path)).to eq(false)
end
+
+ context 'when save throws an exception' do
+ before do
+ expect(subject).to receive(:save_upload).and_raise(SocketError.new)
+ end
+
+ it 'logs a saver error' do
+ allow(Gitlab::Export::Logger).to receive(:info).with(anything).and_call_original
+ expect(Gitlab::Export::Logger).to receive(:info).with(
+ hash_including(
+ message: 'Export archive saver failed',
+ exportable_class: 'Project',
+ 'correlation_id' => anything
+ )).and_call_original
+
+ subject.save
+ end
+ end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 0fbdc09a206..978118ed1b1 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -714,7 +714,7 @@ RSpec.describe Notify do
describe 'project access requested' do
let(:project) do
create(:project, :public) do |project|
- project.add_maintainer(project.owner)
+ project.add_maintainer(project.first_owner)
end
end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index bb8d476f257..5bd69ad9fad 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -394,7 +394,7 @@ RSpec.describe Ability do
describe '.project_disabled_features_rules' do
let(:project) { create(:project, :wiki_disabled) }
- subject { described_class.policy_for(project.owner, project) }
+ subject { described_class.policy_for(project.first_owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 31c7c7a44bc..e08fe196d65 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -851,7 +851,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state, :clean_git
context 'when project is destroyed' do
let(:subject) do
- Projects::DestroyService.new(project, project.owner).execute
+ Projects::DestroyService.new(project, project.first_owner).execute
end
it_behaves_like 'deletes all build_trace_chunk and data in redis'
diff --git a/spec/models/ci/job_token/project_scope_link_spec.rb b/spec/models/ci/job_token/project_scope_link_spec.rb
index 8d7bb44bd16..8494446476f 100644
--- a/spec/models/ci/job_token/project_scope_link_spec.rb
+++ b/spec/models/ci/job_token/project_scope_link_spec.rb
@@ -88,4 +88,11 @@ RSpec.describe Ci::JobToken::ProjectScopeLink do
it { is_expected.to be_nil }
end
end
+
+ context 'loose foreign key on ci_job_token_project_scope_links.source_project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:ci_job_token_project_scope_link, source_project: parent) }
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 8c0f843fdea..9ca4092bba7 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -3427,7 +3427,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
create(:ci_pipeline,
project: project,
sha: project.commit('master').sha,
- user: project.owner)
+ user: project.first_owner)
end
before do
@@ -4502,7 +4502,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#reset_source_bridge!' do
let(:pipeline) { create(:ci_pipeline, :created, project: project) }
- subject(:reset_bridge) { pipeline.reset_source_bridge!(project.owner) }
+ subject(:reset_bridge) { pipeline.reset_source_bridge!(project.first_owner) }
context 'when the pipeline is a child pipeline and the bridge is depended' do
let!(:parent_pipeline) { create(:ci_pipeline) }
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index c9ae8519c63..4ac8720780c 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe Ci::Trigger do
end
it_behaves_like 'includes Limitable concern' do
- subject { build(:ci_trigger, owner: project.owner, project: project) }
+ subject { build(:ci_trigger, owner: project.first_owner, project: project) }
end
context 'loose foreign key on ci_triggers.owner_id' do
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 2176eea75bc..4d3a2fac0fc 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -486,7 +486,7 @@ eos
it 'uses the CachedMarkdownField cache instead of the Mentionable cache', :use_clean_rails_redis_caching do
expect(commit.title_html).not_to be_present
- commit.all_references(project.owner).all
+ commit.all_references(project.first_owner).all
expect(commit.title_html).to be_present
expect(Rails.cache.read("banzai/commit:#{commit.id}/safe_message/single_line")).to be_nil
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 6cfd7def013..27281060935 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe ContainerRepository do
+RSpec.describe ContainerRepository, :aggregate_failures do
using RSpec::Parameterized::TableSyntax
let(:group) { create(:group, name: 'group') }
@@ -36,7 +36,282 @@ RSpec.describe ContainerRepository do
describe 'validations' do
it { is_expected.to validate_presence_of(:migration_retries_count) }
it { is_expected.to validate_numericality_of(:migration_retries_count).is_greater_than_or_equal_to(0) }
- it { is_expected.to validate_presence_of(:migration_state) }
+
+ it { is_expected.to validate_inclusion_of(:migration_aborted_in_state).in_array(ContainerRepository::ACTIVE_MIGRATION_STATES) }
+ it { is_expected.to allow_value(nil).for(:migration_aborted_in_state) }
+
+ context 'migration_state' do
+ it { is_expected.to validate_presence_of(:migration_state) }
+ it { is_expected.to validate_inclusion_of(:migration_state).in_array(ContainerRepository::MIGRATION_STATES) }
+
+ describe 'pre_importing' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'pre_importing')).to be_invalid
+ expect(build(:container_repository, :pre_importing)).to be_valid
+ end
+ end
+
+ describe 'pre_import_done' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'pre_import_done')).to be_invalid
+ expect(build(:container_repository, :pre_import_done)).to be_valid
+ end
+ end
+
+ describe 'importing' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'importing')).to be_invalid
+ expect(build(:container_repository, :importing)).to be_valid
+ end
+ end
+
+ describe 'import_skipped' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'import_skipped')).to be_invalid
+ expect(build(:container_repository, :import_skipped)).to be_valid
+ end
+ end
+
+ describe 'import_aborted' do
+ it 'validates expected attributes' do
+ expect(build(:container_repository, migration_state: 'import_aborted')).to be_invalid
+ expect(build(:container_repository, :import_aborted)).to be_valid
+ end
+ end
+ end
+ end
+
+ context ':migration_state state_machine' do
+ shared_examples 'no action when feature flag is disabled' do
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(container_registry_migration_phase2_enabled: false)
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+
+ shared_examples 'transitioning to pre_importing', skip_pre_import_success: true do
+ before do
+ repository.update_column(:migration_pre_import_done_at, Time.zone.now)
+ end
+
+ it_behaves_like 'no action when feature flag is disabled'
+
+ context 'successful pre_import request' do
+ it 'sets migration_pre_import_started_at and resets migration_pre_import_done_at' do
+ expect(repository).to receive(:migration_pre_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_pre_import_started_at }
+ .and change { repository.migration_pre_import_done_at }.to(nil)
+
+ expect(repository).to be_pre_importing
+ end
+ end
+
+ context 'failed pre_import request' do
+ it 'sets migration_pre_import_started_at and resets migration_pre_import_done_at' do
+ expect(repository).to receive(:migration_pre_import).and_return(:error)
+
+ expect { subject }.to change { repository.reload.migration_pre_import_started_at }
+ .and change { repository.migration_aborted_at }
+ .and change { repository.migration_pre_import_done_at }.to(nil)
+
+ expect(repository.migration_aborted_in_state).to eq('pre_importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+ end
+
+ shared_examples 'transitioning to importing', skip_import_success: true do
+ before do
+ repository.update_columns(migration_import_done_at: Time.zone.now)
+ end
+
+ context 'successful import request' do
+ it 'sets migration_import_started_at and resets migration_import_done_at' do
+ expect(repository).to receive(:migration_import).and_return(:ok)
+
+ expect { subject }.to change { repository.reload.migration_import_started_at }
+ .and change { repository.migration_import_done_at }.to(nil)
+
+ expect(repository).to be_importing
+ end
+ end
+
+ context 'failed import request' do
+ it 'sets migration_import_started_at and resets migration_import_done_at' do
+ expect(repository).to receive(:migration_import).and_return(:error)
+
+ expect { subject }.to change { repository.reload.migration_import_started_at }
+ .and change { repository.migration_aborted_at }
+
+ expect(repository.migration_aborted_in_state).to eq('importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+ end
+
+ shared_examples 'transitioning out of import_aborted' do
+ it 'resets migration_aborted_at and migration_aborted_in_state' do
+ expect { subject }.to change { repository.reload.migration_aborted_in_state }.to(nil)
+ .and change { repository.migration_aborted_at }.to(nil)
+ end
+ end
+
+ shared_examples 'transitioning from allowed states' do |allowed_states|
+ ContainerRepository::MIGRATION_STATES.each do |state|
+ result = allowed_states.include?(state)
+
+ context "when transitioning from #{state}" do
+ let(:repository) { create(:container_repository, state.to_sym) }
+
+ it "returns #{result}" do
+ expect(subject).to eq(result)
+ end
+ end
+ end
+ end
+
+ describe '#start_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.start_pre_import }
+
+ before do |example|
+ unless example.metadata[:skip_pre_import_success]
+ allow(repository).to receive(:migration_pre_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[default]
+ it_behaves_like 'transitioning to pre_importing'
+ end
+
+ describe '#retry_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :import_aborted) }
+
+ subject { repository.retry_pre_import }
+
+ before do |example|
+ unless example.metadata[:skip_pre_import_success]
+ allow(repository).to receive(:migration_pre_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning to pre_importing'
+ it_behaves_like 'transitioning out of import_aborted'
+ end
+
+ describe '#finish_pre_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :pre_importing) }
+
+ subject { repository.finish_pre_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing]
+
+ it 'sets migration_pre_import_done_at' do
+ expect { subject }.to change { repository.reload.migration_pre_import_done_at }
+
+ expect(repository).to be_pre_import_done
+ end
+ end
+
+ describe '#start_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :pre_import_done) }
+
+ subject { repository.start_import }
+
+ before do |example|
+ unless example.metadata[:skip_import_success]
+ allow(repository).to receive(:migration_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_import_done]
+ it_behaves_like 'transitioning to importing'
+ end
+
+ describe '#retry_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :import_aborted) }
+
+ subject { repository.retry_import }
+
+ before do |example|
+ unless example.metadata[:skip_import_success]
+ allow(repository).to receive(:migration_import).and_return(:ok)
+ end
+ end
+
+ it_behaves_like 'transitioning from allowed states', %w[import_aborted]
+ it_behaves_like 'transitioning to importing'
+ it_behaves_like 'no action when feature flag is disabled'
+ end
+
+ describe '#finish_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :importing) }
+
+ subject { repository.finish_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[importing]
+
+ it 'sets migration_import_done_at' do
+ expect { subject }.to change { repository.reload.migration_import_done_at }
+
+ expect(repository).to be_import_done
+ end
+ end
+
+ describe '#already_migrated' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.already_migrated }
+
+ it_behaves_like 'transitioning from allowed states', %w[default]
+
+ it 'sets migration_import_done_at' do
+ subject
+
+ expect(repository).to be_import_done
+ end
+ end
+
+ describe '#abort_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository, :importing) }
+
+ subject { repository.abort_import }
+
+ it_behaves_like 'transitioning from allowed states', %w[pre_importing importing]
+
+ it 'sets migration_aborted_at and migration_aborted_at and increments the retry count' do
+ expect { subject }.to change { repository.migration_aborted_at }
+ .and change { repository.reload.migration_retries_count }.by(1)
+
+ expect(repository.migration_aborted_in_state).to eq('importing')
+ expect(repository).to be_import_aborted
+ end
+ end
+
+ describe '#skip_import' do
+ let_it_be_with_reload(:repository) { create(:container_repository) }
+
+ subject { repository.skip_import(reason: :too_many_retries) }
+
+ it_behaves_like 'transitioning from allowed states', %w[default pre_importing importing]
+
+ it 'sets migration_skipped_at and migration_skipped_reason' do
+ expect { subject }.to change { repository.reload.migration_skipped_at }
+
+ expect(repository.migration_skipped_reason).to eq('too_many_retries')
+ expect(repository).to be_import_skipped
+ end
+
+ it 'raises and error if a reason is not given' do
+ expect { repository.skip_import }.to raise_error(ArgumentError)
+ end
+ end
end
describe '#tag' do
@@ -209,6 +484,46 @@ RSpec.describe ContainerRepository do
end
end
+ context 'registry migration' do
+ shared_examples 'handling the migration step' do |step|
+ let(:client_response) { :foobar }
+
+ before do
+ allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(true)
+ end
+
+ it 'returns the same response as the client' do
+ expect(repository.gitlab_api_client)
+ .to receive(step).with(repository.path).and_return(client_response)
+ expect(subject).to eq(client_response)
+ end
+
+ context 'when the gitlab_api feature is not supported' do
+ before do
+ allow(repository.gitlab_api_client).to receive(:supports_gitlab_api?).and_return(false)
+ end
+
+ it 'returns :error' do
+ expect(repository.gitlab_api_client).not_to receive(step)
+
+ expect(subject).to eq(:error)
+ end
+ end
+ end
+
+ describe '#migration_pre_import' do
+ subject { repository.migration_pre_import }
+
+ it_behaves_like 'handling the migration step', :pre_import_repository
+ end
+
+ describe '#migration_import' do
+ subject { repository.migration_import }
+
+ it_behaves_like 'handling the migration step', :import_repository
+ end
+ end
+
describe '.build_from_path' do
let(:registry_path) do
ContainerRegistry::Path.new(project.full_path + '/some/image')
@@ -505,20 +820,14 @@ RSpec.describe ContainerRepository do
end
describe '#migration_importing?' do
- let_it_be_with_reload(:container_repository) { create(:container_repository, migration_state: 'importing')}
-
subject { container_repository.migration_importing? }
- it { is_expected.to eq(true) }
+ ContainerRepository::MIGRATION_STATES.each do |state|
+ context "when in #{state} migration_state" do
+ let(:container_repository) { create(:container_repository, state.to_sym)}
- context 'when not importing' do
- before do
- # Technical debt: when the state machine is added, we should iterate through all possible states
- # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78499
- container_repository.update_column(:migration_state, 'default')
+ it { is_expected.to eq(state == 'importing') }
end
-
- it { is_expected.to eq(false) }
end
end
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 1b9b38a0932..1db1171401c 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -161,7 +161,7 @@ RSpec.describe EnvironmentStatus do
let!(:build) { create(:ci_build, :with_deployment, :deploy_to_production, pipeline: pipeline) }
let(:environment) { build.deployment.environment }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
context 'when environment is created on a forked project', :sidekiq_inline do
let(:project) { create(:project, :repository) }
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 9119ef83034..f099015e63e 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe Event do
expect(instance).to receive(:reset_project_activity)
end
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
end
end
@@ -34,7 +34,7 @@ RSpec.describe Event do
it 'updates the project last_repository_updated_at and updated_at' do
project.touch(:last_repository_updated_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
- event = create_push_event(project, project.owner)
+ event = create_push_event(project, project.first_owner)
project.reload
@@ -47,7 +47,7 @@ RSpec.describe Event do
it 'does not update the project last_repository_updated_at' do
project.update!(last_repository_updated_at: 1.year.ago)
- create(:closed_issue_event, project: project, author: project.owner)
+ create(:closed_issue_event, project: project, author: project.first_owner)
project.reload
@@ -63,14 +63,14 @@ RSpec.describe Event do
project.reload # a reload removes fractions of seconds
expect do
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
project.reload
end.not_to change { project.last_repository_updated_at }
end
end
describe 'after_create UserInteractedProject.track' do
- let(:event) { build(:push_event, project: project, author: project.owner) }
+ let(:event) { build(:push_event, project: project, author: project.first_owner) }
it 'passes event to UserInteractedProject.track' do
expect(UserInteractedProject).to receive(:track).with(event)
@@ -157,7 +157,7 @@ RSpec.describe Event do
describe "Push event" do
let(:project) { create(:project, :private) }
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
let(:event) { create_push_event(project, user) }
it do
@@ -173,7 +173,7 @@ RSpec.describe Event do
describe '#target_title' do
let_it_be(:project) { create(:project) }
- let(:author) { project.owner }
+ let(:author) { project.first_owner }
let(:target) { nil }
let(:event) do
@@ -746,7 +746,7 @@ RSpec.describe Event do
target = kind == :project ? nil : build(kind, **extra_data)
- [kind, build(:event, :created, author: project.owner, project: project, target: target)]
+ [kind, build(:event, :created, author: project.first_owner, project: project, target: target)]
end
end
@@ -830,7 +830,7 @@ RSpec.describe Event do
expect(project).not_to receive(:update_column)
.with(:last_activity_at, a_kind_of(Time))
- create_push_event(project, project.owner)
+ create_push_event(project, project.first_owner)
end
end
@@ -838,7 +838,7 @@ RSpec.describe Event do
it 'updates the project' do
project.touch(:last_activity_at, time: 1.year.ago) # rubocop: disable Rails/SkipsModelValidations
- event = create_push_event(project, project.owner)
+ event = create_push_event(project, project.first_owner)
project.reload
diff --git a/spec/models/external_pull_request_spec.rb b/spec/models/external_pull_request_spec.rb
index b141600c4fd..82da7cdf34b 100644
--- a/spec/models/external_pull_request_spec.rb
+++ b/spec/models/external_pull_request_spec.rb
@@ -236,4 +236,11 @@ RSpec.describe ExternalPullRequest do
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :external_pull_request }
end
+
+ context 'loose foreign key on external_pull_requests.project_id' do
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:parent) { create(:project) }
+ let!(:model) { create(:external_pull_request, project: parent) }
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 05ee2166245..4bc4df02c24 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -1249,7 +1249,7 @@ RSpec.describe Group do
let(:common_id) { [Project.maximum(:id).to_i, Namespace.maximum(:id).to_i].max + 999 }
let!(:group) { create(:group, id: common_id) }
let!(:unrelated_project) { create(:project, id: common_id) }
- let(:user) { unrelated_project.owner }
+ let(:user) { unrelated_project.first_owner }
it 'returns correct access level' do
expect(shared_group_parent.max_member_access_for_user(user)).to eq(Gitlab::Access::NO_ACCESS)
diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb
index d67bd8debce..183082bab26 100644
--- a/spec/models/issue_collection_spec.rb
+++ b/spec/models/issue_collection_spec.rb
@@ -50,7 +50,9 @@ RSpec.describe IssueCollection do
end
end
- context 'using a user that is the owner of a project' do
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
+ context 'using a user that is an owner of a project' do
it 'returns the issues of the project' do
expect(collection.updatable_by_user(project.namespace.owner))
.to eq([issue1, issue2])
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index b91b299467d..5af42cc67ea 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -887,6 +887,8 @@ RSpec.describe Issue do
end
end
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
context 'with an owner' do
before do
project.add_maintainer(user)
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9d9cca0678a..f7d32dc1a7b 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -617,7 +617,7 @@ RSpec.describe Note do
let(:note) do
create :note,
noteable: ext_issue, project: ext_proj,
- note: "mentioned in #{ext_proj.owner.to_reference}",
+ note: "mentioned in #{ext_proj.first_owner.to_reference}",
system: true
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 0d1ab22c520..d78d0a56f10 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -871,6 +871,8 @@ RSpec.describe Project, factory_default: :keep do
end
describe 'reference methods' do
+ # TODO update when we have multiple owners of a project
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/350605
let_it_be(:owner) { create(:user, name: 'Gitlab') }
let_it_be(:namespace) { create(:namespace, name: 'Sample namespace', path: 'sample-namespace', owner: owner) }
let_it_be(:project) { create(:project, name: 'Sample project', path: 'sample-project', namespace: namespace) }
@@ -2874,7 +2876,7 @@ RSpec.describe Project, factory_default: :keep do
end
before do
- project.repository.rm_branch(project.owner, branch.name)
+ project.repository.rm_branch(project.first_owner, branch.name)
end
subject { project.latest_pipeline(branch.name) }
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index d5af2d80906..bfdebbc33df 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -80,7 +80,7 @@ RSpec.describe ProjectTeam do
describe 'owner methods' do
context 'personal project' do
let(:project) { create(:project) }
- let(:owner) { project.owner }
+ let(:owner) { project.first_owner }
specify { expect(project.team.owners).to contain_exactly(owner) }
specify { expect(project.team.owner?(owner)).to be_truthy }
@@ -108,10 +108,12 @@ RSpec.describe ProjectTeam do
let(:project) { create(:project) }
it 'returns project members' do
+ # TODO this can be updated when we have multiple project owners
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/350605
user = create(:user)
project.add_guest(user)
- expect(project.team.members).to contain_exactly(user, project.owner)
+ expect(project.team.members).to contain_exactly(user, project.first_owner)
end
it 'returns project members of a specified level' do
@@ -129,7 +131,7 @@ RSpec.describe ProjectTeam do
group_access: Gitlab::Access::GUEST)
expect(project.team.members)
- .to contain_exactly(group_member.user, project.owner)
+ .to contain_exactly(group_member.user, project.first_owner)
end
it 'returns invited members of a group of a specified level' do
diff --git a/spec/policies/ci/pipeline_policy_spec.rb b/spec/policies/ci/pipeline_policy_spec.rb
index 9a65823c950..b68bb966820 100644
--- a/spec/policies/ci/pipeline_policy_spec.rb
+++ b/spec/policies/ci/pipeline_policy_spec.rb
@@ -89,7 +89,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
let(:project) { create(:project, :public) }
context 'when user has owner access' do
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
it 'is enabled' do
expect(policy).to be_allowed :destroy_pipeline
@@ -107,7 +107,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
let(:project) { create(:project, :public) }
context 'when user has owner access' do
- let(:user) { project.owner }
+ let(:user) { project.first_owner }
it 'is enabled' do
expect(policy).to be_allowed :read_pipeline_variable
@@ -129,7 +129,7 @@ RSpec.describe Ci::PipelinePolicy, :models do
end
context 'when user is developer and it is not the creator of the pipeline' do
- let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.owner) }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, user: project.first_owner) }
before do
project.add_developer(user)
diff --git a/spec/policies/namespaces/project_namespace_policy_spec.rb b/spec/policies/namespaces/project_namespace_policy_spec.rb
index f6fe4ae552a..f1022747fab 100644
--- a/spec/policies/namespaces/project_namespace_policy_spec.rb
+++ b/spec/policies/namespaces/project_namespace_policy_spec.rb
@@ -28,7 +28,7 @@ RSpec.describe Namespaces::ProjectNamespacePolicy do
end
context 'parent owner' do
- let_it_be(:current_user) { parent.owner }
+ let_it_be(:current_user) { parent.first_owner }
it { is_expected.to be_disallowed(*permissions) }
end
diff --git a/spec/policies/project_member_policy_spec.rb b/spec/policies/project_member_policy_spec.rb
index aebbe685bb3..12b3e60fdb2 100644
--- a/spec/policies/project_member_policy_spec.rb
+++ b/spec/policies/project_member_policy_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe ProjectMemberPolicy do
end
context 'when user is project owner' do
- let(:member_user) { project.owner }
+ let(:member_user) { project.first_owner }
let(:member) { project.members.find_by!(user: member_user) }
it { is_expected.to be_allowed(:read_project) }
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 38e4e18c894..793b1fffd5f 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -117,7 +117,7 @@ RSpec.describe ProjectPolicy do
end
describe 'for unconfirmed user' do
- let(:current_user) { project.owner.tap { |u| u.update!(confirmed_at: nil) } }
+ let(:current_user) { project.first_owner.tap { |u| u.update!(confirmed_at: nil) } }
it 'disallows to modify pipelines' do
expect_disallowed(:create_pipeline)
@@ -144,7 +144,7 @@ RSpec.describe ProjectPolicy do
end
describe 'for project owner' do
- let(:current_user) { project.owner }
+ let(:current_user) { project.first_owner }
it 'allows :destroy_pipeline' do
expect(current_user.can?(:destroy_pipeline, pipeline)).to be_truthy
diff --git a/spec/rubocop/cop/file_decompression_spec.rb b/spec/rubocop/cop/file_decompression_spec.rb
new file mode 100644
index 00000000000..7be1a784001
--- /dev/null
+++ b/spec/rubocop/cop/file_decompression_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require_relative '../../../rubocop/cop/file_decompression'
+
+RSpec.describe RuboCop::Cop::FileDecompression do
+ subject(:cop) { described_class.new }
+
+ it 'does not flag when using a system command not related to file decompression' do
+ expect_no_offenses('system("ls")')
+ end
+
+ described_class::FORBIDDEN_COMMANDS.map { [_1, '^' * _1.length] }.each do |cmd, len|
+ it "flags the when using '#{cmd}' system command" do
+ expect_offense(<<~SOURCE)
+ system('#{cmd}')
+ ^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ exec('#{cmd}')
+ ^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ Kernel.spawn('#{cmd}')
+ ^^^^^^^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ IO.popen('#{cmd}')
+ ^^^^^^^^^^#{len}^^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+ end
+
+ it "flags the when using '#{cmd}' subshell command" do
+ expect_offense(<<~SOURCE)
+ `#{cmd}`
+ ^#{len}^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+
+ expect_offense(<<~SOURCE)
+ %x(#{cmd})
+ ^^^#{len}^ While extracting files check for symlink to avoid arbitrary file reading[...]
+ SOURCE
+ end
+ end
+end
diff --git a/spec/services/service_ping/submit_service_ping_service_spec.rb b/spec/services/service_ping/submit_service_ping_service_spec.rb
index 2971c9a9309..b5c28e0346a 100644
--- a/spec/services/service_ping/submit_service_ping_service_spec.rb
+++ b/spec/services/service_ping/submit_service_ping_service_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe ServicePing::SubmitService do
shared_examples 'does not send a blank usage ping payload' do
it do
- expect(Gitlab::HTTP).not_to receive(:post)
+ expect(Gitlab::HTTP).not_to receive(:post).with(subject.url, any_args)
expect { subject.execute }.to raise_error(described_class::SubmissionError) do |error|
expect(error.message).to include('Usage data is blank')
@@ -129,6 +129,7 @@ RSpec.describe ServicePing::SubmitService do
stub_usage_data_connections
stub_database_flavor_check
stub_application_setting(usage_ping_enabled: true)
+ stub_response(body: nil, url: subject.error_url, status: 201)
end
context 'and user requires usage stats consent' do
@@ -277,7 +278,8 @@ RSpec.describe ServicePing::SubmitService do
context 'if payload service fails' do
before do
stub_response(body: with_dev_ops_score_params)
- allow(ServicePing::BuildPayloadService).to receive(:execute).and_raise(described_class::SubmissionError, 'SubmissionError')
+ allow(ServicePing::BuildPayloadService).to receive_message_chain(:new, :execute)
+ .and_raise(described_class::SubmissionError, 'SubmissionError')
end
it 'calls UsageData .data method' do
@@ -287,6 +289,15 @@ RSpec.describe ServicePing::SubmitService do
subject.execute
end
+
+ it 'submits error' do
+ expect(Gitlab::HTTP).to receive(:post).with(subject.url, any_args)
+ .and_call_original
+ expect(Gitlab::HTTP).to receive(:post).with(subject.error_url, any_args)
+ .and_call_original
+
+ subject.execute
+ end
end
context 'calls BuildPayloadService first' do
diff --git a/spec/services/update_container_registry_info_service_spec.rb b/spec/services/update_container_registry_info_service_spec.rb
index 740e53b0472..64071e79508 100644
--- a/spec/services/update_container_registry_info_service_spec.rb
+++ b/spec/services/update_container_registry_info_service_spec.rb
@@ -48,6 +48,7 @@ RSpec.describe UpdateContainerRegistryInfoService do
before do
stub_registry_info({})
+ stub_supports_gitlab_api(false)
end
it 'uses a token with no access permissions' do
@@ -63,6 +64,7 @@ RSpec.describe UpdateContainerRegistryInfoService do
context 'when unabled to detect the container registry type' do
it 'sets the application settings to their defaults' do
stub_registry_info({})
+ stub_supports_gitlab_api(false)
subject
@@ -76,20 +78,23 @@ RSpec.describe UpdateContainerRegistryInfoService do
context 'when able to detect the container registry type' do
context 'when using the GitLab container registry' do
it 'updates application settings accordingly' do
- stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a,b,c])
+ stub_registry_info(vendor: 'gitlab', version: '2.9.1-gitlab', features: %w[a b c])
+ stub_supports_gitlab_api(true)
subject
application_settings.reload
expect(application_settings.container_registry_vendor).to eq('gitlab')
expect(application_settings.container_registry_version).to eq('2.9.1-gitlab')
- expect(application_settings.container_registry_features).to eq(%w[a,b,c])
+ expect(application_settings.container_registry_features)
+ .to match_array(%W[a b c #{ContainerRegistry::GitlabApiClient::REGISTRY_GITLAB_V1_API_FEATURE}])
end
end
context 'when using a third-party container registry' do
it 'updates application settings accordingly' do
stub_registry_info(vendor: 'other', version: nil, features: nil)
+ stub_supports_gitlab_api(false)
subject
@@ -112,4 +117,10 @@ RSpec.describe UpdateContainerRegistryInfoService do
allow(client).to receive(:registry_info).and_return(output)
end
end
+
+ def stub_supports_gitlab_api(output)
+ allow_next_instance_of(ContainerRegistry::GitlabApiClient) do |client|
+ allow(client).to receive(:supports_gitlab_api?).and_return(output)
+ end
+ end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 17f091278e5..2b3b7b114c8 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -112,11 +112,11 @@ RSpec.configure do |config|
# falling back to all tests when there is no `:focus` example.
config.filter_run focus: true
config.run_all_when_everything_filtered = true
-
- # Re-run failures locally with `--only-failures`
- config.example_status_persistence_file_path = './spec/examples.txt'
end
+ # Re-run failures locally with `--only-failures`
+ config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', './spec/examples.txt')
+
config.define_derived_metadata(file_path: %r{(ee)?/spec/.+_spec\.rb\z}) do |metadata|
location = metadata[:location]
@@ -208,7 +208,9 @@ RSpec.configure do |config|
config.exceptions_to_hard_fail = [DeprecationToolkitEnv::DeprecationBehaviors::SelectiveRaise::RaiseDisallowedDeprecation]
end
- if ENV['FLAKY_RSPEC_GENERATE_REPORT']
+ require_relative '../tooling/rspec_flaky/config'
+
+ if RspecFlaky::Config.generate_report?
require_relative '../tooling/rspec_flaky/listener'
config.reporter.register_listener(
diff --git a/spec/support/flaky_tests.rb b/spec/support/flaky_tests.rb
index 5ce55c47aab..4df0d23bfc3 100644
--- a/spec/support/flaky_tests.rb
+++ b/spec/support/flaky_tests.rb
@@ -4,14 +4,14 @@ return unless ENV['CI']
return if ENV['SKIP_FLAKY_TESTS_AUTOMATICALLY'] == "false"
return if ENV['CI_MERGE_REQUEST_LABELS'].to_s.include?('pipeline:run-flaky-tests')
+require_relative '../../tooling/rspec_flaky/config'
require_relative '../../tooling/rspec_flaky/report'
RSpec.configure do |config|
$flaky_test_example_ids = begin # rubocop:disable Style/GlobalVars
- raise "$SUITE_FLAKY_RSPEC_REPORT_PATH is empty." if ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'].to_s.empty?
- raise "#{ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']} doesn't exist" unless File.exist?(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'])
+ raise "#{RspecFlaky::Config.suite_flaky_examples_report_path} doesn't exist" unless File.exist?(RspecFlaky::Config.suite_flaky_examples_report_path)
- RspecFlaky::Report.load(ENV['SUITE_FLAKY_RSPEC_REPORT_PATH']).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] }
+ RspecFlaky::Report.load(RspecFlaky::Config.suite_flaky_examples_report_path).map { |_, flaky_test_data| flaky_test_data.to_h[:example_id] }
rescue => e # rubocop:disable Style/RescueStandardError
puts e
[]
@@ -29,8 +29,9 @@ RSpec.configure do |config|
end
config.after(:suite) do
- next unless ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH']
+ next unless RspecFlaky::Config.skipped_flaky_tests_report_path
+ next if $skipped_flaky_tests_report.empty? # rubocop:disable Style/GlobalVars
- File.write(ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'], "#{$skipped_flaky_tests_report.join("\n")}\n") # rubocop:disable Style/GlobalVars
+ File.write(RspecFlaky::Config.skipped_flaky_tests_report_path, "#{ENV['CI_JOB_URL']}\n#{$skipped_flaky_tests_report.join("\n")}\n\n") # rubocop:disable Style/GlobalVars
end
end
diff --git a/spec/support/helpers/features/iteration_helpers.rb b/spec/support/helpers/features/iteration_helpers.rb
new file mode 100644
index 00000000000..8e1d252f55f
--- /dev/null
+++ b/spec/support/helpers/features/iteration_helpers.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+module IterationHelpers
+ def iteration_period(iteration)
+ "#{iteration.start_date.to_s(:medium)} - #{iteration.due_date.to_s(:medium)}"
+ end
+end
diff --git a/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb
new file mode 100644
index 00000000000..a87a3247b95
--- /dev/null
+++ b/spec/support/shared_contexts/lib/container_registry/client_shared_context.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+RSpec.shared_context 'container registry client' do
+ let(:token) { '12345' }
+ let(:options) { { token: token } }
+ let(:registry_api_url) { 'http://container-registry' }
+ let(:client) { described_class.new(registry_api_url, options) }
+ let(:push_blob_headers) do
+ {
+ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+ 'Authorization' => "bearer #{token}",
+ 'Content-Type' => 'application/octet-stream',
+ 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
+ }
+ end
+
+ let(:headers_with_accept_types) do
+ {
+ 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json',
+ 'Authorization' => "bearer #{token}",
+ 'User-Agent' => "GitLab/#{Gitlab::VERSION}"
+ }
+ end
+
+ let(:expected_faraday_headers) { { user_agent: "GitLab/#{Gitlab::VERSION}" } }
+ let(:expected_faraday_request_options) { Gitlab::HTTP::DEFAULT_TIMEOUT_OPTIONS }
+end
diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
index 4713e2fc789..ac5029edc82 100644
--- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb
@@ -1142,7 +1142,7 @@ RSpec.shared_examples 'a container registry auth service' do
end
context 'when importing' do
- let_it_be(:container_repository) { create(:container_repository, :root, migration_state: 'importing') }
+ let_it_be(:container_repository) { create(:container_repository, :root, :importing) }
let_it_be(:current_project) { container_repository.project }
let_it_be(:current_user) { create(:user) }
diff --git a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
index 4b44d991d89..34542668730 100644
--- a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
+++ b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb
@@ -18,7 +18,9 @@ RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
allow(File).to receive(:exist?).with(filter_tests_file).and_return(true)
allow(File).to receive(:read).and_call_original
allow(File).to receive(:read).with(filter_tests_file).and_return(filter_tests)
- allow(subject).to receive(:exec)
+ allow(Process).to receive(:spawn)
+ allow(Process).to receive(:wait)
+ allow(Process).to receive(:last_status).and_return(double(exitstatus: 0))
end
subject { described_class.new(allocator: allocator, filter_tests_file: filter_tests_file, rspec_args: rspec_args) }
@@ -86,7 +88,7 @@ RSpec.describe Tooling::ParallelRSpecRunner do # rubocop:disable RSpec/FilePath
end
def expect_command(cmd)
- expect(subject).to receive(:exec).with(*cmd)
+ expect(Process).to receive(:spawn).with(*cmd)
end
end
end
diff --git a/spec/tooling/rspec_flaky/config_spec.rb b/spec/tooling/rspec_flaky/config_spec.rb
index 12b5ed74cb2..c95e5475d66 100644
--- a/spec/tooling/rspec_flaky/config_spec.rb
+++ b/spec/tooling/rspec_flaky/config_spec.rb
@@ -11,9 +11,10 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('FLAKY_RSPEC_GENERATE_REPORT', nil)
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', nil)
stub_env('FLAKY_RSPEC_REPORT_PATH', nil)
stub_env('NEW_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('SKIPPED_FLAKY_TESTS_REPORT_PATH', nil)
# Ensure the behavior is the same locally and on CI (where Rails is defined since we run this test as part of the whole suite), i.e. Rails isn't defined
allow(described_class).to receive(:rails_path).and_wrap_original do |method, path|
path
@@ -51,15 +52,15 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
end
describe '.suite_flaky_examples_report_path' do
- context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is not set" do
+ context "when ENV['FLAKY_RSPEC_SUITE_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.suite_flaky_examples_report_path).to eq('rspec_flaky/suite-report.json')
+ expect(described_class.suite_flaky_examples_report_path).to eq('rspec/flaky/suite-report.json')
end
end
- context "when ENV['SUITE_FLAKY_RSPEC_REPORT_PATH'] is set" do
+ context "when ENV['FLAKY_RSPEC_SUITE_REPORT_PATH'] is set" do
before do
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', 'foo/suite-report.json')
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', 'foo/suite-report.json')
end
it 'returns the value of the env variable' do
@@ -71,7 +72,7 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
describe '.flaky_examples_report_path' do
context "when ENV['FLAKY_RSPEC_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.flaky_examples_report_path).to eq('rspec_flaky/report.json')
+ expect(described_class.flaky_examples_report_path).to eq('rspec/flaky/report.json')
end
end
@@ -89,7 +90,7 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
describe '.new_flaky_examples_report_path' do
context "when ENV['NEW_FLAKY_RSPEC_REPORT_PATH'] is not set" do
it 'returns the default path' do
- expect(described_class.new_flaky_examples_report_path).to eq('rspec_flaky/new-report.json')
+ expect(described_class.new_flaky_examples_report_path).to eq('rspec/flaky/new-report.json')
end
end
@@ -103,4 +104,22 @@ RSpec.describe RspecFlaky::Config, :aggregate_failures do
end
end
end
+
+ describe '.skipped_flaky_tests_report_path' do
+ context "when ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] is not set" do
+ it 'returns the default path' do
+ expect(described_class.skipped_flaky_tests_report_path).to eq('rspec/flaky/skipped_flaky_tests_report.txt')
+ end
+ end
+
+ context "when ENV['SKIPPED_FLAKY_TESTS_REPORT_PATH'] is set" do
+ before do
+ stub_env('SKIPPED_FLAKY_TESTS_REPORT_PATH', 'foo/skipped_flaky_tests_report.txt')
+ end
+
+ it 'returns the value of the env variable' do
+ expect(described_class.skipped_flaky_tests_report_path).to eq('foo/skipped_flaky_tests_report.txt')
+ end
+ end
+ end
end
diff --git a/spec/tooling/rspec_flaky/listener_spec.rb b/spec/tooling/rspec_flaky/listener_spec.rb
index 51a815dafbf..62bbe53cac1 100644
--- a/spec/tooling/rspec_flaky/listener_spec.rb
+++ b/spec/tooling/rspec_flaky/listener_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe RspecFlaky::Listener, :aggregate_failures do
before do
# Stub these env variables otherwise specs don't behave the same on the CI
stub_env('CI_JOB_URL', nil)
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', nil)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', nil)
end
describe '#initialize' do
@@ -73,11 +73,11 @@ RSpec.describe RspecFlaky::Listener, :aggregate_failures do
it_behaves_like 'a valid Listener instance'
end
- context 'when SUITE_FLAKY_RSPEC_REPORT_PATH is set' do
+ context 'when FLAKY_RSPEC_SUITE_REPORT_PATH is set' do
let(:report_file_path) { 'foo/report.json' }
before do
- stub_env('SUITE_FLAKY_RSPEC_REPORT_PATH', report_file_path)
+ stub_env('FLAKY_RSPEC_SUITE_REPORT_PATH', report_file_path)
end
context 'and report file exists' do