diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-25 18:11:55 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-25 18:11:55 +0000 |
commit | 2b2d833ab3e78f8c9f626af950a16d43fc38c9f8 (patch) | |
tree | 13c101679f3cda5808affea46709207a24f4a3c9 /spec | |
parent | 7d8d5a3dab415672a41ab29c3bfa9581f275dc50 (diff) | |
download | gitlab-ce-2b2d833ab3e78f8c9f626af950a16d43fc38c9f8.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
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 |