diff options
Diffstat (limited to 'spec/lib/gitlab')
11 files changed, 390 insertions, 15 deletions
diff --git a/spec/lib/gitlab/checks/snippet_check_spec.rb b/spec/lib/gitlab/checks/snippet_check_spec.rb index 7cb29debd1e..7cfcde7183f 100644 --- a/spec/lib/gitlab/checks/snippet_check_spec.rb +++ b/spec/lib/gitlab/checks/snippet_check_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Checks::SnippetCheck do let(:newrev) { '0000000000000000000000000000000000000000' } it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.') end end @@ -27,7 +27,7 @@ describe Gitlab::Checks::SnippetCheck do let(:oldrev) { '0000000000000000000000000000000000000000' } it 'raises an error' do - expect { subject.exec }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can not create or delete branches.') + expect { subject.exec }.to raise_error(Gitlab::GitAccess::ForbiddenError, 'You can not create or delete branches.') end end end diff --git a/spec/lib/gitlab/git_access_snippet_spec.rb b/spec/lib/gitlab/git_access_snippet_spec.rb index de19db38176..a68ac8ee8fe 100644 --- a/spec/lib/gitlab/git_access_snippet_spec.rb +++ b/spec/lib/gitlab/git_access_snippet_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::GitAccessSnippet do let(:actor) { build(:deploy_key) } it 'does not allow push and pull access' do - expect { pull_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:authentication_mechanism]) + expect { pull_access_check }.to raise_forbidden(described_class::ERROR_MESSAGES[:authentication_mechanism]) end end @@ -76,8 +76,8 @@ describe Gitlab::GitAccessSnippet do it 'blocks access when the user did not accept terms' do message = /must accept the Terms of Service in order to perform this action/ - expect { push_access_check }.to raise_unauthorized(message) - expect { pull_access_check }.to raise_unauthorized(message) + expect { push_access_check }.to raise_forbidden(message) + expect { pull_access_check }.to raise_forbidden(message) end it 'allows access when the user accepted the terms' do @@ -101,13 +101,13 @@ describe Gitlab::GitAccessSnippet do if Ability.allowed?(user, :update_snippet, snippet) expect { push_access_check }.not_to raise_error else - expect { push_access_check }.to raise_error(described_class::UnauthorizedError) + expect { push_access_check }.to raise_error(described_class::ForbiddenError) end if Ability.allowed?(user, :read_snippet, snippet) expect { pull_access_check }.not_to raise_error else - expect { pull_access_check }.to raise_error(described_class::UnauthorizedError) + expect { pull_access_check }.to raise_error(described_class::ForbiddenError) end end end @@ -154,7 +154,7 @@ describe Gitlab::GitAccessSnippet do with_them do it "respects accessibility" do - error_class = described_class::UnauthorizedError + error_class = described_class::ForbiddenError if Ability.allowed?(user, :update_snippet, snippet) expect { push_access_check }.not_to raise_error @@ -180,7 +180,7 @@ describe Gitlab::GitAccessSnippet do allow(::Gitlab::Database).to receive(:read_only?).and_return(true) allow(::Gitlab::Geo).to receive(:secondary_with_primary?).and_return(true) - expect { push_access_check }.to raise_unauthorized(/You can't push code to a read-only GitLab instance/) + expect { push_access_check }.to raise_forbidden(/You can't push code to a read-only GitLab instance/) end end @@ -198,10 +198,10 @@ describe Gitlab::GitAccessSnippet do it 'raises error if SnippetCheck raises error' do expect_next_instance_of(Gitlab::Checks::SnippetCheck) do |check| - allow(check).to receive(:exec).and_raise(Gitlab::GitAccess::UnauthorizedError, 'foo') + allow(check).to receive(:exec).and_raise(Gitlab::GitAccess::ForbiddenError, 'foo') end - expect { push_access_check }.to raise_unauthorized('foo') + expect { push_access_check }.to raise_forbidden('foo') end end @@ -215,7 +215,7 @@ describe Gitlab::GitAccessSnippet do raise_error(Gitlab::GitAccess::NotFoundError, Gitlab::GitAccess::ERROR_MESSAGES[:project_not_found]) end - def raise_unauthorized(message) - raise_error(Gitlab::GitAccess::UnauthorizedError, message) + def raise_forbidden(message) + raise_error(Gitlab::GitAccess::ForbiddenError, message) end end diff --git a/spec/lib/gitlab/graphql/docs/renderer_spec.rb b/spec/lib/gitlab/graphql/docs/renderer_spec.rb new file mode 100644 index 00000000000..5ba70bb8f0a --- /dev/null +++ b/spec/lib/gitlab/graphql/docs/renderer_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Graphql::Docs::Renderer do + describe '#contents' do + # Returns a Schema that uses the given `type` + def mock_schema(type) + query_type = Class.new(GraphQL::Schema::Object) do + graphql_name 'QueryType' + + field :foo, type, null: true + end + + GraphQL::Schema.define(query: query_type) + end + + let_it_be(:template) { Rails.root.join('lib/gitlab/graphql/docs/templates/', 'default.md.haml') } + + subject(:contents) do + described_class.new( + mock_schema(type).graphql_definition, + output_dir: nil, + template: template + ).contents + end + + context 'A type with a field with a [Array] return type' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'ArrayTest' + + field :foo, [GraphQL::STRING_TYPE], null: false, description: 'A description' + end + end + + specify do + expectation = <<~DOC + ## ArrayTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `foo` | String! => Array | A description | + DOC + + is_expected.to include(expectation) + end + end + + context 'A type with fields defined in reverse alphabetical order' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'OrderingTest' + + field :foo, GraphQL::STRING_TYPE, null: false, description: 'A description of foo field' + field :bar, GraphQL::STRING_TYPE, null: false, description: 'A description of bar field' + end + end + + specify do + expectation = <<~DOC + ## OrderingTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `bar` | String! | A description of bar field | + | `foo` | String! | A description of foo field | + DOC + + is_expected.to include(expectation) + end + end + + context 'A type with a deprecated field' do + let(:type) do + Class.new(GraphQL::Schema::Object) do + graphql_name 'DeprecatedTest' + + field :foo, GraphQL::STRING_TYPE, null: false, deprecation_reason: 'This is deprecated', description: 'A description' + end + end + + specify do + expectation = <<~DOC + ## DeprecatedTest + + | Name | Type | Description | + | --- | ---- | ---------- | + | `foo` **{warning-solid}** | String! | **Deprecated:** This is deprecated | + DOC + + is_expected.to include(expectation) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index db45b9c42fd..d97d76cf35e 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -192,6 +192,7 @@ ci_pipelines: - environments - chat_data - source_pipeline +- ref_status - source_bridge - source_job - sourced_pipelines @@ -359,6 +360,7 @@ project: - ci_pipelines - all_pipelines - stages +- ci_refs - builds - runner_projects - runners diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index f5a6ea4d5b0..2dd45d18ee9 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -89,6 +89,17 @@ describe Gitlab::IncomingEmail do it 'does not match emails with extra bits' do expect(described_class.key_from_address('somereplies+somekey@example.com.someotherdomain.com')).to be nil end + + context 'when a custom wildcard address is used' do + let(:wildcard_address) { 'custom.address+%{key}@example.com' } + + it 'finds key if email matches address pattern' do + key = described_class.key_from_address( + 'custom.address+foo@example.com', wildcard_address: wildcard_address + ) + expect(key).to eq('foo') + end + end end context 'self.key_from_fallback_message_id' do diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb new file mode 100644 index 00000000000..b6e47afc7e8 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/client_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Client, :clean_gitlab_redis_queues do + let(:worker_class) do + Class.new do + def self.name + 'TestDeduplicationWorker' + end + + include ApplicationWorker + + def perform(*args) + end + end + end + + before do + stub_const('TestDeduplicationWorker', worker_class) + end + + describe '#call' do + it 'adds a correct duplicate tag to the jobs', :aggregate_failures do + TestDeduplicationWorker.bulk_perform_async([['args1'], ['args2'], ['args1']]) + + job1, job2, job3 = TestDeduplicationWorker.jobs + + expect(job1['duplicate-of']).to be_nil + expect(job2['duplicate-of']).to be_nil + expect(job3['duplicate-of']).to eq(job1['jid']) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb new file mode 100644 index 00000000000..2334439461e --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/duplicate_job_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob, :clean_gitlab_redis_queues do + subject(:duplicate_job) do + described_class.new(job, queue) + end + + let(:job) { { 'class' => 'AuthorizedProjectsWorker', 'args' => [1], 'jid' => '123' } } + let(:queue) { 'authorized_projects' } + + let(:idempotency_key) do + hash = Digest::SHA256.hexdigest("#{job['class']}:#{job['args'].join('-')}") + "#{Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE}:duplicate:#{queue}:#{hash}" + end + + describe '#schedule' do + it 'calls schedule on the strategy' do + expect do |block| + expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy| + expect(strategy).to receive(:schedule).with(job, &block) + end + + duplicate_job.schedule(&block) + end.to yield_control + end + end + + describe '#perform' do + it 'calls perform on the strategy' do + expect do |block| + expect_next_instance_of(Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting) do |strategy| + expect(strategy).to receive(:perform).with(job, &block) + end + + duplicate_job.perform(&block) + end.to yield_control + end + end + + describe '#check!' do + context 'when there was no job in the queue yet' do + it { expect(duplicate_job.check!).to eq('123') } + + it "adds a key with ttl set to #{described_class::DUPLICATE_KEY_TTL}" do + expect { duplicate_job.check! } + .to change { read_idempotency_key_with_ttl(idempotency_key) } + .from([nil, -2]) + .to(['123', be_within(1).of(described_class::DUPLICATE_KEY_TTL)]) + end + end + + context 'when there was already a job with same arguments in the same queue' do + before do + set_idempotency_key(idempotency_key, 'existing-key') + end + + it { expect(duplicate_job.check!).to eq('existing-key') } + + it "does not change the existing key's TTL" do + expect { duplicate_job.check! } + .not_to change { read_idempotency_key_with_ttl(idempotency_key) } + .from(['existing-key', -1]) + end + + it 'sets the existing jid' do + duplicate_job.check! + + expect(duplicate_job.existing_jid).to eq('existing-key') + end + end + end + + describe '#delete!' do + context "when we didn't track the definition" do + it { expect { duplicate_job.delete! }.not_to raise_error } + end + + context 'when the key exists in redis' do + before do + set_idempotency_key(idempotency_key, 'existing-key') + end + + it 'removes the key from redis' do + expect { duplicate_job.delete! } + .to change { read_idempotency_key_with_ttl(idempotency_key) } + .from(['existing-key', -1]) + .to([nil, -2]) + end + end + end + + describe '#duplicate?' do + it "raises an error if the check wasn't performed" do + expect { duplicate_job.duplicate? }.to raise_error /Call `#check!` first/ + end + + it 'returns false if the existing jid equals the job jid' do + duplicate_job.check! + + expect(duplicate_job.duplicate?).to be(false) + end + + it 'returns false if the existing jid is different from the job jid' do + set_idempotency_key(idempotency_key, 'a different jid') + duplicate_job.check! + + expect(duplicate_job.duplicate?).to be(true) + end + end + + def set_idempotency_key(key, value = '1') + Sidekiq.redis { |r| r.set(key, value) } + end + + def read_idempotency_key_with_ttl(key) + Sidekiq.redis do |redis| + redis.pipelined do |p| + p.get(key) + p.ttl(key) + end + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb new file mode 100644 index 00000000000..0ea248fbcf1 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/server_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Server, :clean_gitlab_redis_queues do + let(:worker_class) do + Class.new do + def self.name + 'TestDeduplicationWorker' + end + + include ApplicationWorker + + def perform(*args) + end + end + end + + before do + stub_const('TestDeduplicationWorker', worker_class) + end + + around do |example| + Sidekiq::Testing.inline! { example.run } + end + + before(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.add described_class + end + end + + after(:context) do + Sidekiq::Testing.server_middleware do |chain| + chain.remove described_class + end + end + + describe '#call' do + it 'removes the stored job from redis' do + bare_job = { 'class' => 'TestDeduplicationWorker', 'args' => ['hello'] } + job_definition = Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob.new(bare_job.dup, 'test_deduplication') + + expect(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob) + .to receive(:new).with(a_hash_including(bare_job), 'test_deduplication') + .and_return(job_definition).twice # once in client middleware + expect(job_definition).to receive(:delete!).and_call_original + + TestDeduplicationWorker.perform_async('hello') + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb new file mode 100644 index 00000000000..f40e829f9a5 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies::UntilExecuting do + let(:fake_duplicate_job) do + instance_double(Gitlab::SidekiqMiddleware::DuplicateJobs::DuplicateJob) + end + + subject(:strategy) { described_class.new(fake_duplicate_job) } + + describe '#schedule' do + it 'checks for duplicates before yielding' do + expect(fake_duplicate_job).to receive(:check!).ordered.and_return('a jid') + expect(fake_duplicate_job).to receive(:duplicate?).ordered.and_return(false) + expect { |b| strategy.schedule({}, &b) }.to yield_control + end + + it 'adds the jid of the existing job to the job hash' do + allow(fake_duplicate_job).to receive(:check!).and_return('the jid') + job_hash = {} + + expect(fake_duplicate_job).to receive(:duplicate?).and_return(true) + expect(fake_duplicate_job).to receive(:existing_jid).and_return('the jid') + + strategy.schedule(job_hash) {} + + expect(job_hash).to include('duplicate-of' => 'the jid') + end + end + + describe '#perform' do + it 'deletes the lock before executing' do + expect(fake_duplicate_job).to receive(:delete!).ordered + expect { |b| strategy.perform({}, &b) }.to yield_control + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb new file mode 100644 index 00000000000..6ecc2a3a5f8 --- /dev/null +++ b/spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +describe Gitlab::SidekiqMiddleware::DuplicateJobs::Strategies do + describe '.for' do + it 'returns the right class for `until_executing`' do + expect(described_class.for(:until_executing)).to eq(described_class::UntilExecuting) + end + + it 'raises an UnknownStrategyError when passing an unknown key' do + expect { described_class.for(:unknown) }.to raise_error(described_class::UnknownStrategyError) + end + end +end diff --git a/spec/lib/gitlab/sidekiq_middleware_spec.rb b/spec/lib/gitlab/sidekiq_middleware_spec.rb index 19242d25e27..2f325fd5052 100644 --- a/spec/lib/gitlab/sidekiq_middleware_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware_spec.rb @@ -46,7 +46,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::MemoryKiller, Gitlab::SidekiqMiddleware::RequestStoreMiddleware, Gitlab::SidekiqMiddleware::WorkerContext::Server, - Gitlab::SidekiqMiddleware::AdminMode::Server + Gitlab::SidekiqMiddleware::AdminMode::Server, + Gitlab::SidekiqMiddleware::DuplicateJobs::Server ] end let(:enabled_sidekiq_middlewares) { all_sidekiq_middlewares - disabled_sidekiq_middlewares } @@ -117,7 +118,8 @@ describe Gitlab::SidekiqMiddleware do Gitlab::SidekiqMiddleware::ClientMetrics, Gitlab::SidekiqMiddleware::WorkerContext::Client, Labkit::Middleware::Sidekiq::Client, - Gitlab::SidekiqMiddleware::AdminMode::Client + Gitlab::SidekiqMiddleware::AdminMode::Client, + Gitlab::SidekiqMiddleware::DuplicateJobs::Client ] end |